"use client" import { useState, useEffect, useCallback } from "react" import { useAuth } from "@/contexts/auth-context" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Star, Eye, Heart, ThumbsUp, Info, MessageSquare, User, Calendar, Building, TrendingUp, Users, BarChart3, Brain, ImageIcon, Mic, Settings, Zap, Target, Bookmark, Lightbulb, Search, Plus, X, ChevronLeft, ChevronRight, ArrowLeft, Trophy, Award, Medal, } from "lucide-react" import { FavoriteButton } from "./favorite-button" import { ReviewSystem } from "./reviews/review-system" interface AppDetailDialogProps { open: boolean onOpenChange: (open: boolean) => void app: { id: number name: string type: string department: string description: string icon: any creator: string featured: boolean judgeScore: number } } // Usage statistics data - empty for production const getAppUsageStats = (appId: string, startDate: string, endDate: string) => { return { dailyUsers: 0, weeklyUsers: 0, monthlyUsers: 0, totalSessions: 0, topDepartments: [], trendData: [], } } export function AppDetailDialog({ open, onOpenChange, app }: AppDetailDialogProps) { const { user, addToRecentApps, getAppLikes, incrementViewCount, getViewCount, getAppRating } = useAuth() const [currentRating, setCurrentRating] = useState(getAppRating(app.id.toString())) const [reviewCount, setReviewCount] = useState(0) 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 [isLoadingStats, setIsLoadingStats] = useState(false) // Date range for usage trends const [startDate, setStartDate] = useState(() => { const date = new Date() date.setDate(date.getDate() - 6) // Default to last 7 days return date.toISOString().split("T")[0] }) const [endDate, setEndDate] = useState(() => { return new Date().toISOString().split("T")[0] }) // 圖標映射函數 const getIconComponent = (iconName: string) => { const iconMap: { [key: string]: any } = { 'Bot': Brain, 'ImageIcon': ImageIcon, 'Mic': Mic, 'MessageSquare': MessageSquare, 'Settings': Settings, 'Zap': Zap, 'TrendingUp': TrendingUp, 'Star': Star, 'Heart': Heart, 'Eye': Eye, 'Trophy': Trophy, 'Award': Award, 'Medal': Medal, 'Target': Target, 'Users': Users, 'Lightbulb': Lightbulb, 'Search': Search, 'Plus': Plus, 'X': X, 'ChevronLeft': ChevronLeft, 'ChevronRight': ChevronRight, 'ArrowLeft': ArrowLeft } return iconMap[iconName] || Brain // 預設使用 Brain 圖標 } const IconComponent = getIconComponent(app.icon || 'Bot') const likes = (app as any).likesCount || 0 const views = (app as any).viewsCount || 0 const rating = (app as any).rating || 0 const reviewsCount = (app as any).reviewsCount || 0 // 使用從 API 載入的實際數據 const usageStats = appStats.usage 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 loadAppStats = useCallback(async (customStartDate?: string, customEndDate?: string) => { if (!app.id) return setIsLoadingStats(true) try { // 構建查詢參數 const params = new URLSearchParams() if (customStartDate) params.append('startDate', customStartDate) if (customEndDate) params.append('endDate', customEndDate) const url = `/api/apps/${app.id}/stats${params.toString() ? `?${params.toString()}` : ''}` const response = await fetch(url) const data = await response.json() if (data.success) { setAppStats(data.data) setCurrentRating(data.data.basic.rating) setReviewCount(data.data.basic.reviewCount) } } catch (error) { console.error('載入應用統計數據錯誤:', error) } finally { setIsLoadingStats(false) } }, [app.id]) const handleRatingUpdate = useCallback(async (newRating: number, newReviewCount: number) => { setCurrentRating(newRating) setReviewCount(newReviewCount) // Reload stats after rating update await loadAppStats() }, [loadAppStats]) // 處理日期範圍變更 const handleDateRangeChange = useCallback(async () => { if (startDate && endDate) { await loadAppStats(startDate, endDate) } }, [startDate, endDate, loadAppStats]) // 當對話框打開時載入統計數據 useEffect(() => { if (open && app.id) { loadAppStats() } }, [open, app.id, loadAppStats]) // 當日期範圍變更時重新載入使用趨勢數據 useEffect(() => { if (open && app.id && startDate && endDate) { handleDateRangeChange() } }, [startDate, endDate, open, app.id, handleDateRangeChange]) const handleTryApp = async () => { console.log('handleTryApp 被調用', { user: user?.id, appId: app.id }) if (user) { addToRecentApps(app.id.toString()) // 記錄用戶活動 try { console.log('開始記錄用戶活動...') const response = await fetch('/api/user/activity', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ userId: user.id, action: 'view', resourceType: 'app', resourceId: app.id.toString(), details: { appName: app.name, timestamp: new Date().toISOString() } }) }) if (response.ok) { console.log('活動記錄成功') } else { console.error('活動記錄失敗:', response.status, response.statusText) } } catch (error) { console.error('記錄活動失敗:', error) } } else { console.log('用戶未登入,跳過活動記錄') } // Increment view count when trying the app await incrementViewCount(app.id.toString()) // Reload stats after incrementing view count await loadAppStats() // Get app URL from database or fallback to default URLs const appUrl = (app as any).appUrl || (app as any).app_url if (appUrl) { // Ensure URL has protocol const url = appUrl.startsWith('http') ? appUrl : `https://${appUrl}` window.open(url, "_blank", "noopener,noreferrer") } else { // Fallback to default URLs for testing const defaultUrls: Record = { "1": "https://dify.example.com/chat-assistant", "2": "https://image-gen.example.com", "3": "https://speech.example.com", "4": "https://recommend.example.com", "5": "https://text-analysis.example.com", "6": "https://ai-writing.example.com", } const fallbackUrl = defaultUrls[app.id.toString()] if (fallbackUrl) { window.open(fallbackUrl, "_blank", "noopener,noreferrer") } else { console.warn('No app URL found for app:', app.id) // Show a toast or alert to user alert('此應用暫無可用連結') } } } // Helper function to group data by month/year for section headers const getDateSections = (trendData: any[]) => { const sections: { [key: string]: { startIndex: number; endIndex: number; label: string } } = {} trendData.forEach((day, index) => { const date = new Date(day.date) const yearMonth = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}` const label = date.toLocaleDateString("zh-TW", { year: "numeric", month: "long" }) if (!sections[yearMonth]) { sections[yearMonth] = { startIndex: index, endIndex: index, label } } else { sections[yearMonth].endIndex = index } }) return sections } return (
{app.name} {app.description}
{app.type} {app.department} {app.featured && ( 精選 )}
{likes} 讚
{views} 瀏覽
{Number(rating).toFixed(1)} {reviewsCount > 0 && ({reviewsCount} 評價)}
應用概覽 使用統計 用戶評價 應用詳情

開發者

{app.creator}

所屬部門

{app.department}

發布日期

{(app as any).createdAt ? new Date((app as any).createdAt).toLocaleDateString('zh-TW', { year: 'numeric', month: 'long', day: 'numeric' }) : '未知'}

功能特色

  • • 基於 {app.type} 技術
  • • 適用於 {app.department} 部門
  • • 提供智能化解決方案
  • • 支援多種使用場景

詳細描述

{app.description || '暫無詳細描述'}

{/* 基本統計數據 */}
總瀏覽量
{isLoadingStats ? '...' : views}

累計瀏覽次數

按讚數
{isLoadingStats ? '...' : likes}

用戶按讚數量

收藏數
{isLoadingStats ? '...' : (appStats.basic as any).favorites || 0}

用戶收藏數量

平均評分
{isLoadingStats ? '...' : Number(rating).toFixed(1)}

用戶評分

評價數量
{isLoadingStats ? '...' : reviewsCount}

用戶評價總數

{/* 使用趨勢 */}
今日使用者
{isLoadingStats ? '...' : appStats.usage.dailyUsers}

活躍用戶數

本週使用者
{isLoadingStats ? '...' : appStats.usage.weeklyUsers}

週活躍用戶

總使用次數
{isLoadingStats ? '...' : appStats.usage.totalSessions.toLocaleString()}

累計使用次數

{/* Usage Trends with Date Range */}
使用趨勢 查看指定時間範圍內的使用者活躍度
setStartDate(e.target.value)} className="w-36" max={endDate} />
setEndDate(e.target.value)} className="w-36" min={startDate} max={new Date().toISOString().split("T")[0]} />
{isLoadingStats ? (

載入使用趨勢數據中...

) : usageStats.trendData && usageStats.trendData.length > 0 ? ( <> {/* Chart Container with Horizontal Scroll */}
{/* Month/Year Section Headers */}
{(() => { const sections = getDateSections(usageStats.trendData) const totalBars = usageStats.trendData.length return Object.entries(sections).map(([key, section]) => { const width = ((section.endIndex - section.startIndex + 1) / totalBars) * 100 const left = (section.startIndex / totalBars) * 100 return (
{section.label}
) }) })()}
{/* Chart Bars */}
{usageStats.trendData.map((day: any, index: number) => { const maxUsers = Math.max(...usageStats.trendData.map((d: any) => d.users)) const minUsers = Math.min(...usageStats.trendData.map((d: any) => d.users)) const range = maxUsers - minUsers const normalizedHeight = range > 0 ? ((day.users - minUsers) / range) * 70 + 15 : 40 const currentDate = new Date(day.date) const prevDate = index > 0 ? new Date((usageStats.trendData[index - 1] as any).date) : null // Check if this is the start of a new month/year for divider const isNewMonth = !prevDate || currentDate.getMonth() !== prevDate.getMonth() || currentDate.getFullYear() !== prevDate.getFullYear() return (
{/* Month divider line */} {isNewMonth && index > 0 && (
)}
{/* Value label */}
{day.users}
{/* Consistent day-only labels */}
{currentDate.getDate()}日
) })}
{/* Scroll Hint */} {usageStats.trendData && usageStats.trendData.length > 20 && (
💡 提示:圖表可左右滑動查看更多數據
)} ) : (

在選定的日期範圍內暫無使用數據

請嘗試選擇其他日期範圍

)}
{/* Department Usage */} 部門使用分布 各部門使用者比例
{usageStats.topDepartments && usageStats.topDepartments.length > 0 ? ( usageStats.topDepartments.map((dept: any, index: number) => { const totalUsers = usageStats.topDepartments.reduce((sum: number, d: any) => sum + d.count, 0) const percentage = totalUsers > 0 ? Math.round((dept.count / totalUsers) * 100) : 0 return (
{dept.department || '未知部門'}
{dept.count} 人 {percentage}%
) }) ) : (

暫無部門使用數據

)}
) }