Files
ai-showcase-platform/components/auth/activity-records-dialog.tsx

409 lines
18 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useAuth } from "@/contexts/auth-context"
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Progress } from "@/components/ui/progress"
import {
BarChart3, Clock, Heart, ImageIcon, MessageSquare, FileText, TrendingUp, Trash2, RefreshCw,
Bot, Mic, Settings, Zap, Star, Eye, Target, Users, Lightbulb, Search, Database, Shield, Cpu,
Globe, Layers, PieChart, Activity, Calendar, Code, Command, Compass, CreditCard, Download,
Edit, ExternalLink, Filter, Flag, Folder, Gift, Home, Info, Key, Link, Lock, Mail, MapPin,
Minus, Monitor, MoreHorizontal, MoreVertical, MousePointer, Navigation, Pause, Play, Plus,
Power, Save, Send, Share, Smartphone, Tablet, Upload, Volume2, Wifi, X, ZoomIn, ZoomOut
} from "lucide-react"
import { useState, useEffect } from "react"
interface ActivityRecordsDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
}
// Mock data for demonstration - will be replaced with real data
const mockRecentApps: any[] = []
const mockCategoryData: any[] = []
export function ActivityRecordsDialog({ open, onOpenChange }: ActivityRecordsDialogProps) {
const {
user,
getViewCount,
getAppLikes,
getUserLikeHistory,
getAppLikesInPeriod
} = useAuth()
const [recentApps, setRecentApps] = useState<any[]>([])
const [categoryData, setCategoryData] = useState<any[]>([])
const [userStats, setUserStats] = useState({
totalUsage: 0,
favoriteApps: 0,
daysJoined: 0
})
const [isLoading, setIsLoading] = useState(false)
const [isResetting, setIsResetting] = useState(false)
if (!user) return null
// Load user activity data from API
const loadUserActivity = async () => {
if (!user) return
setIsLoading(true)
try {
const response = await fetch(`/api/user/activity?userId=${user.id}`)
const data = await response.json()
if (data.success) {
setRecentApps(data.data.recentApps || [])
setCategoryData(data.data.categoryStats || [])
setUserStats(data.data.userStats || {
totalUsage: 0,
favoriteApps: 0,
daysJoined: 0
})
}
} catch (error) {
console.error('載入用戶活動數據錯誤:', error)
} finally {
setIsLoading(false)
}
}
// Load data when dialog opens
useEffect(() => {
if (open && user) {
loadUserActivity()
}
}, [open, user])
// Reset user activity data
const resetActivityData = async () => {
setIsResetting(true)
try {
// Clear localStorage data
if (typeof window !== "undefined") {
localStorage.removeItem("appViews")
localStorage.removeItem("appLikes")
localStorage.removeItem("userLikes")
localStorage.removeItem("appLikesOld")
localStorage.removeItem("appRatings")
}
// Reset user's recent apps and favorites
if (user) {
const updatedUser = {
...user,
recentApps: [],
favoriteApps: [],
totalLikes: 0,
totalViews: 0
}
localStorage.setItem("user", JSON.stringify(updatedUser))
// Reload the page to refresh all data
window.location.reload()
}
} catch (error) {
console.error("Error resetting activity data:", error)
} finally {
setIsResetting(false)
}
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-5xl max-h-[85vh] overflow-hidden">
<DialogHeader>
<DialogTitle className="text-2xl font-bold flex items-center gap-2">
<BarChart3 className="w-6 h-6" />
</DialogTitle>
</DialogHeader>
<Tabs defaultValue="recent" className="w-full">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="recent">使</TabsTrigger>
<TabsTrigger value="statistics"></TabsTrigger>
</TabsList>
<TabsContent value="recent" className="space-y-4 max-h-[60vh] overflow-y-auto">
<div>
<h3 className="text-lg font-semibold mb-2">使</h3>
<p className="text-sm text-muted-foreground mb-4"> AI </p>
{isLoading ? (
<div className="text-center py-12">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-purple-500 mx-auto mb-2"></div>
<p className="text-gray-500">...</p>
</div>
) : recentApps.length > 0 ? (
<div className="grid gap-4">
{recentApps.map((app) => {
// 圖標映射函數
const getIconComponent = (iconName: string) => {
const iconMap: { [key: string]: any } = {
'Bot': Bot,
'ImageIcon': ImageIcon,
'Mic': Mic,
'MessageSquare': MessageSquare,
'Settings': Settings,
'Zap': Zap,
'TrendingUp': TrendingUp,
'Star': Star,
'Heart': Heart,
'Eye': Eye,
'Target': Target,
'Users': Users,
'Lightbulb': Lightbulb,
'Search': Search,
'BarChart3': BarChart3,
'Database': Database,
'Shield': Shield,
'Cpu': Cpu,
'FileText': FileText,
'Globe': Globe,
'Layers': Layers,
'PieChart': PieChart,
'Activity': Activity,
'Calendar': Calendar,
'Clock': Clock,
'Code': Code,
'Command': Command,
'Compass': Compass,
'CreditCard': CreditCard,
'Download': Download,
'Edit': Edit,
'ExternalLink': ExternalLink,
'Filter': Filter,
'Flag': Flag,
'Folder': Folder,
'Gift': Gift,
'Home': Home,
'Info': Info,
'Key': Key,
'Link': Link,
'Lock': Lock,
'Mail': Mail,
'MapPin': MapPin,
'Minus': Minus,
'Monitor': Monitor,
'MoreHorizontal': MoreHorizontal,
'MoreVertical': MoreVertical,
'MousePointer': MousePointer,
'Navigation': Navigation,
'Pause': Pause,
'Play': Play,
'Plus': Plus,
'Power': Power,
'RefreshCw': RefreshCw,
'Save': Save,
'Send': Send,
'Share': Share,
'Smartphone': Smartphone,
'Tablet': Tablet,
'Trash2': Trash2,
'Upload': Upload,
'Volume2': Volume2,
'Wifi': Wifi,
'X': X,
'ZoomIn': ZoomIn,
'ZoomOut': ZoomOut,
}
return iconMap[iconName] || MessageSquare
}
const IconComponent = getIconComponent(app.icon || 'MessageSquare')
const lastUsedDate = new Date(app.lastUsed)
const now = new Date()
const diffTime = Math.abs(now.getTime() - lastUsedDate.getTime())
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
return (
<Card key={app.id} className="hover:shadow-md transition-shadow">
<CardContent className="flex items-center justify-between p-4">
<div className="flex items-center space-x-4">
<div className={`p-3 rounded-lg bg-gradient-to-r ${app.iconColor || 'from-blue-500 to-purple-500'}`}>
<IconComponent className="w-6 h-6 text-white" />
</div>
<div>
<h4 className="font-semibold">{app.name}</h4>
<p className="text-sm text-muted-foreground">by {app.creator}</p>
<div className="flex items-center gap-4 mt-1">
<Badge variant="secondary" className="text-xs">
{app.type}
</Badge>
<span className="text-xs text-muted-foreground flex items-center gap-1">
<BarChart3 className="w-3 h-3" />
使 {app.usageCount}
</span>
</div>
</div>
</div>
<div className="text-right">
<p className="text-xs text-muted-foreground mb-2">
{diffDays === 1 ? '昨天' : diffDays < 7 ? `${diffDays}天前` : `${Math.ceil(diffDays / 7)}週前`}
</p>
<Button
size="sm"
variant="outline"
onClick={() => {
// 記錄活動
if (user) {
fetch('/api/user/activity', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userId: user.id,
action: 'view',
resourceType: 'app',
resourceId: app.id,
details: JSON.stringify({ appName: app.name })
})
}).catch(console.error)
}
// 增加查看次數
fetch(`/api/apps/${app.id}/interactions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'view', userId: user?.id })
}).catch(console.error)
// 打開應用
if (app.appUrl && app.appUrl !== '#') {
window.open(app.appUrl, '_blank')
}
}}
>
</Button>
</div>
</CardContent>
</Card>
)
})}
</div>
) : (
<Card>
<CardContent className="text-center py-12">
<MessageSquare className="w-16 h-16 text-gray-400 mx-auto mb-4" />
<h3 className="text-xl font-semibold text-gray-600 mb-2">使</h3>
<p className="text-gray-500 mb-4"> AI 使</p>
<Button
variant="outline"
onClick={() => onOpenChange(false)}
>
</Button>
</CardContent>
</Card>
)}
</div>
</TabsContent>
<TabsContent value="statistics" className="space-y-6 max-h-[60vh] overflow-y-auto">
<div>
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-lg font-semibold mb-2"></h3>
<p className="text-sm text-muted-foreground"></p>
</div>
<Button
variant="outline"
size="sm"
onClick={resetActivityData}
disabled={isResetting}
className="text-red-600 hover:text-red-700 hover:bg-red-50"
>
{isResetting ? (
<RefreshCw className="w-4 h-4 mr-2 animate-spin" />
) : (
<Trash2 className="w-4 h-4 mr-2" />
)}
{isResetting ? "重置中..." : "清空數據"}
</Button>
</div>
{/* Statistics Cards */}
<div className="grid grid-cols-2 lg:grid-cols-3 gap-4 mb-6">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">使</CardTitle>
<TrendingUp className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{isNaN(userStats.totalUsage) ? 0 : userStats.totalUsage}</div>
<p className="text-xs text-muted-foreground">
{(isNaN(userStats.totalUsage) ? 0 : userStats.totalUsage) > 0 ? "累計使用" : "尚未使用任何應用"}
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium"></CardTitle>
<Heart className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{isNaN(userStats.favoriteApps) ? 0 : userStats.favoriteApps}</div>
<p className="text-xs text-muted-foreground">
{(isNaN(userStats.favoriteApps) ? 0 : userStats.favoriteApps) > 0 ? "個人收藏" : "尚未收藏任何應用"}
</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base"></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{isNaN(userStats.daysJoined) ? 0 : userStats.daysJoined}</div>
<p className="text-xs text-muted-foreground">
{(isNaN(userStats.daysJoined) ? 0 : userStats.daysJoined) > 0 ? "已加入平台" : "今天剛加入"}
</p>
</CardContent>
</Card>
</div>
{/* Category Usage */}
<Card>
<CardHeader>
<CardTitle className="text-base">使</CardTitle>
<CardDescription>使</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{categoryData.length > 0 ? (
categoryData.map((category, index) => (
<div key={index} className="space-y-2">
<div className="flex items-center justify-between text-sm">
<div className="flex items-center gap-2">
<div className={`w-3 h-3 rounded-full ${category.color}`} />
<span className="font-medium">{category.name}</span>
</div>
<span className="text-muted-foreground">{category.usage}%</span>
</div>
<Progress value={category.usage} className="h-2" />
</div>
))
) : (
<div className="text-center py-8">
<BarChart3 className="w-12 h-12 text-gray-400 mx-auto mb-3" />
<p className="text-gray-500 text-sm">使</p>
<p className="text-gray-400 text-xs mt-1">使</p>
</div>
)}
</CardContent>
</Card>
</div>
</TabsContent>
</Tabs>
</DialogContent>
</Dialog>
)
}