diff --git a/app/api/admin/analytics/route.ts b/app/api/admin/analytics/route.ts new file mode 100644 index 0000000..2c957fe --- /dev/null +++ b/app/api/admin/analytics/route.ts @@ -0,0 +1,211 @@ +import { NextRequest, NextResponse } from "next/server" +import { DatabaseService } from "@/lib/services/database-service" + +const dbService = new DatabaseService() + +export async function GET(request: NextRequest) { + try { + // 獲取總用戶數 + const totalUsersResult = await dbService.query(` + SELECT COUNT(*) as total FROM users WHERE is_active = TRUE + `) + const totalUsers = totalUsersResult[0]?.total || 0 + + // 獲取今日活躍用戶數(今日有登入記錄的用戶) + const today = new Date().toISOString().split('T')[0] + const todayActiveUsersResult = await dbService.query(` + SELECT COUNT(DISTINCT user_id) as count + FROM activity_logs + WHERE DATE(created_at) = ? AND action = 'login' + `, [today]) + const todayActiveUsers = todayActiveUsersResult[0]?.count || 0 + + // 獲取昨日活躍用戶數(用於比較) + const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString().split('T')[0] + const yesterdayActiveUsersResult = await dbService.query(` + SELECT COUNT(DISTINCT user_id) as count + FROM activity_logs + WHERE DATE(created_at) = ? AND action = 'login' + `, [yesterday]) + const yesterdayActiveUsers = yesterdayActiveUsersResult[0]?.count || 0 + + // 計算今日活躍用戶增長率 + const todayActiveGrowth = yesterdayActiveUsers > 0 + ? ((todayActiveUsers - yesterdayActiveUsers) / yesterdayActiveUsers * 100).toFixed(1) + : 0 + + // 獲取平均評分 + const avgRatingResult = await dbService.query(` + SELECT AVG(rating) as avg_rating FROM apps WHERE is_active = TRUE AND rating > 0 + `) + const avgRating = avgRatingResult[0]?.avg_rating || 0 + + // 獲取上週平均評分(用於比較) + const lastWeek = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0] + const lastWeekRatingResult = await dbService.query(` + SELECT AVG(rating) as avg_rating + FROM apps + WHERE is_active = TRUE AND rating > 0 AND created_at < ? + `, [lastWeek]) + const lastWeekRating = lastWeekRatingResult[0]?.avg_rating || 0 + + // 計算評分增長 + const ratingGrowth = lastWeekRating > 0 + ? (avgRating - lastWeekRating).toFixed(1) + : 0 + + // 獲取應用總數 + const totalAppsResult = await dbService.query(` + SELECT COUNT(*) as total FROM apps WHERE is_active = TRUE + `) + const totalApps = totalAppsResult[0]?.total || 0 + + // 獲取本週新增應用數 + const weekStart = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0] + const newThisWeekResult = await dbService.query(` + SELECT COUNT(*) as count + FROM apps + WHERE is_active = TRUE AND DATE(created_at) >= ? + `, [weekStart]) + const newThisWeek = newThisWeekResult[0]?.count || 0 + + // 獲取上月用戶數(用於比較) + const lastMonth = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0] + const lastMonthUsersResult = await dbService.query(` + SELECT COUNT(*) as total + FROM users + WHERE is_active = TRUE AND created_at < ? + `, [lastMonth]) + const lastMonthUsers = lastMonthUsersResult[0]?.total || 0 + + // 計算用戶增長率 + const userGrowth = lastMonthUsers > 0 + ? ((totalUsers - lastMonthUsers) / lastMonthUsers * 100).toFixed(1) + : 0 + + // 獲取近7天的使用趨勢數據 + const dailyUsageData = [] + for (let i = 6; i >= 0; i--) { + const date = new Date(Date.now() - i * 24 * 60 * 60 * 1000) + const dateStr = date.toISOString().split('T')[0] + const dayName = ["日", "一", "二", "三", "四", "五", "六"][date.getDay()] + + // 獲取當日活躍用戶數 + const dailyUsersResult = await dbService.query(` + SELECT COUNT(DISTINCT user_id) as count + FROM activity_logs + WHERE DATE(created_at) = ? AND action IN ('login', 'view') + `, [dateStr]) + const dailyUsers = dailyUsersResult[0]?.count || 0 + + // 獲取當日會話數 + const dailySessionsResult = await dbService.query(` + SELECT COUNT(*) as count + FROM activity_logs + WHERE DATE(created_at) = ? AND action = 'view' + `, [dateStr]) + const dailySessions = dailySessionsResult[0]?.count || 0 + + dailyUsageData.push({ + date: `${date.getMonth() + 1}/${date.getDate()}`, + fullDate: date.toLocaleDateString("zh-TW"), + dayName: dayName, + users: dailyUsers, + sessions: dailySessions, + cpuPeak: Math.min(80, 40 + dailyUsers * 0.1), // 模擬CPU使用率 + avgCpu: Math.min(70, 30 + dailyUsers * 0.08), + memoryPeak: Math.min(75, 35 + dailyUsers * 0.12), + requests: dailySessions * 5 // 模擬請求數 + }) + } + + // 獲取應用類別分布 + const categoryDataResult = await dbService.query(` + SELECT + type as category, + COUNT(*) as app_count, + SUM(views_count) as total_views + FROM apps + WHERE is_active = TRUE + GROUP BY type + ORDER BY app_count DESC + `) + + const totalAppCount = categoryDataResult.reduce((sum, item) => sum + item.app_count, 0) + const categoryData = categoryDataResult.map((item, index) => { + const colors = ["#3b82f6", "#ef4444", "#10b981", "#f59e0b", "#8b5cf6"] + return { + name: item.category, + value: Math.round((item.app_count / totalAppCount) * 100), + color: colors[index % colors.length], + users: Math.round(item.total_views * 0.3), // 估算用戶數 + apps: item.app_count + } + }) + + // 獲取熱門應用排行 + const topAppsResult = await dbService.query(` + SELECT + a.name, + a.views_count as views, + a.rating, + a.type as category + FROM apps a + WHERE a.is_active = TRUE + ORDER BY a.views_count DESC + LIMIT 5 + `) + + const topApps = topAppsResult.map(app => ({ + name: app.name, + views: app.views || 0, + rating: parseFloat(app.rating) || 0, + category: app.category + })) + + // 獲取用戶滿意度數據 + const satisfactionResult = await dbService.query(` + SELECT + AVG(rating) as avg_rating, + COUNT(*) as total_ratings + FROM user_ratings + WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY) + `) + + const satisfaction = satisfactionResult[0] || { avg_rating: 0, total_ratings: 0 } + const satisfactionRate = satisfaction.avg_rating > 0 + ? Math.round((satisfaction.avg_rating / 5) * 100) + : 92 // 預設值 + + return NextResponse.json({ + success: true, + data: { + // 關鍵指標 + totalUsers, + todayActiveUsers, + todayActiveGrowth: parseFloat(todayActiveGrowth), + avgRating: parseFloat(avgRating.toFixed(1)), + ratingGrowth: parseFloat(ratingGrowth), + totalApps, + newThisWeek, + userGrowth: parseFloat(userGrowth), + + // 趨勢數據 + dailyUsageData, + categoryData, + topApps, + + // 滿意度數據 + satisfactionRate, + weeklyFeedback: satisfaction.total_ratings || 0 + } + }) + + } catch (error) { + console.error('獲取分析數據錯誤:', error) + return NextResponse.json( + { success: false, error: '獲取分析數據時發生錯誤' }, + { status: 500 } + ) + } +} diff --git a/app/page.tsx b/app/page.tsx index 9fe3d15..704298a 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -26,6 +26,14 @@ import { ArrowLeft, Plus, X, + Database, + BarChart3, + Camera, + Smartphone, + Monitor, + Globe, + FileText, + Bot, } from "lucide-react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" @@ -279,7 +287,8 @@ export default function AIShowcasePlatform() { // 圖標映射函數 const getIconComponent = (iconName: string) => { const iconMap: { [key: string]: any } = { - 'Bot': Brain, + 'Bot': Bot, + 'Brain': Brain, 'ImageIcon': ImageIcon, 'Mic': Mic, 'MessageSquare': MessageSquare, @@ -300,10 +309,17 @@ export default function AIShowcasePlatform() { 'X': X, 'ChevronLeft': ChevronLeft, 'ChevronRight': ChevronRight, - 'ArrowLeft': ArrowLeft + 'ArrowLeft': ArrowLeft, + 'Database': Database, + 'BarChart3': BarChart3, + 'Camera': Camera, + 'Smartphone': Smartphone, + 'Monitor': Monitor, + 'Globe': Globe, + 'FileText': FileText, } - return iconMap[iconName] || Brain // 預設使用 Brain 圖標 + return iconMap[iconName] || Bot // 預設使用 Bot 圖標 } const getTypeColor = (type: string) => { @@ -946,10 +962,27 @@ export default function AIShowcasePlatform() { 全部部門 + ACBU + AUBU + FAB3 + FNBU HQBU + HRBU + IBU + ICDU + ICBU ITBU MBU1 + MBU5 + PJA + PBU + SBG SBU + 董事會 + 法務室 + 關係企業發展 + 稽核室 + 總經理室 diff --git a/app/register/page.tsx b/app/register/page.tsx index 20fffeb..d210d3f 100644 --- a/app/register/page.tsx +++ b/app/register/page.tsx @@ -309,14 +309,27 @@ export default function RegisterPage() { + ACBU + AUBU + FAB3 + FNBU HQBU + HRBU + IBU + ICDU + ICBU ITBU - RDBU - MDBU - PGBU - SGBU - TGBU - WGBU + MBU1 + MBU5 + PJA + PBU + SBG + SBU + 董事會 + 法務室 + 關係企業發展 + 稽核室 + 總經理室 其他 diff --git a/components/admin/analytics-dashboard.tsx b/components/admin/analytics-dashboard.tsx index 1e190f4..bf2cd8e 100644 --- a/components/admin/analytics-dashboard.tsx +++ b/components/admin/analytics-dashboard.tsx @@ -16,12 +16,28 @@ import { Line, ComposedChart, } from "recharts" -import { Users, Eye, Star, TrendingUp, Clock, Activity, Calendar, AlertTriangle } from "lucide-react" -import { useState } from "react" +import { Users, Eye, Star, TrendingUp, Clock, Activity, Calendar, AlertTriangle, Loader2 } from "lucide-react" +import { useState, useEffect } from "react" export function AnalyticsDashboard() { const [showHistoryModal, setShowHistoryModal] = useState(false) const [selectedDateRange, setSelectedDateRange] = useState("近7天") + const [isLoading, setIsLoading] = useState(true) + const [analyticsData, setAnalyticsData] = useState({ + totalUsers: 0, + todayActiveUsers: 0, + todayActiveGrowth: 0, + avgRating: 0, + ratingGrowth: 0, + totalApps: 0, + newThisWeek: 0, + userGrowth: 0, + dailyUsageData: [], + categoryData: [], + topApps: [], + satisfactionRate: 0, + weeklyFeedback: 0 + }) // 24小時使用數據 - 優化版本 const hourlyData = [ diff --git a/components/admin/app-management.tsx b/components/admin/app-management.tsx index 18b2b2a..673c684 100644 --- a/components/admin/app-management.tsx +++ b/components/admin/app-management.tsx @@ -978,10 +978,27 @@ export function AppManagement() { + ACBU + AUBU + FAB3 + FNBU HQBU + HRBU + IBU + ICDU + ICBU ITBU MBU1 + MBU5 + PJA + PBU + SBG SBU + 董事會 + 法務室 + 關係企業發展 + 稽核室 + 總經理室 @@ -1132,10 +1149,27 @@ export function AppManagement() { + ACBU + AUBU + FAB3 + FNBU HQBU + HRBU + IBU + ICDU + ICBU ITBU MBU1 + MBU5 + PJA + PBU + SBG SBU + 董事會 + 法務室 + 關係企業發展 + 稽核室 + 總經理室 diff --git a/components/admin/competition-management.tsx b/components/admin/competition-management.tsx index 2f299a7..a5291a2 100644 --- a/components/admin/competition-management.tsx +++ b/components/admin/competition-management.tsx @@ -2652,14 +2652,27 @@ export function CompetitionManagement() { 所有部門 + ACBU + AUBU + FAB3 + FNBU HQBU + HRBU + IBU + ICDU + ICBU ITBU MBU1 - MBU2 + MBU5 + PJA + PBU + SBG SBU - 研發部 - 產品部 - 技術部 + 董事會 + 法務室 + 關係企業發展 + 稽核室 + 總經理室 其他 @@ -2924,14 +2937,27 @@ export function CompetitionManagement() { 所有部門 + ACBU + AUBU + FAB3 + FNBU HQBU + HRBU + IBU + ICDU + ICBU ITBU MBU1 - MBU2 + MBU5 + PJA + PBU + SBG SBU - 研發部 - 產品部 - 技術部 + 董事會 + 法務室 + 關係企業發展 + 稽核室 + 總經理室 其他 @@ -5107,10 +5133,27 @@ export function CompetitionManagement() { 所有部門 + ACBU + AUBU + FAB3 + FNBU HQBU + HRBU + IBU + ICDU + ICBU ITBU MBU1 + MBU5 + PJA + PBU + SBG SBU + 董事會 + 法務室 + 關係企業發展 + 稽核室 + 總經理室 @@ -5186,10 +5229,27 @@ export function CompetitionManagement() { 所有部門 + ACBU + AUBU + FAB3 + FNBU HQBU + HRBU + IBU + ICDU + ICBU ITBU MBU1 + MBU5 + PJA + PBU + SBG SBU + 董事會 + 法務室 + 關係企業發展 + 稽核室 + 總經理室 @@ -5296,10 +5356,27 @@ export function CompetitionManagement() { 所有部門 + ACBU + AUBU + FAB3 + FNBU HQBU + HRBU + IBU + ICDU + ICBU ITBU MBU1 + MBU5 + PJA + PBU + SBG SBU + 董事會 + 法務室 + 關係企業發展 + 稽核室 + 總經理室 @@ -6197,10 +6274,27 @@ export function CompetitionManagement() { + ACBU + AUBU + FAB3 + FNBU HQBU + HRBU + IBU + ICDU + ICBU ITBU MBU1 + MBU5 + PJA + PBU + SBG SBU + 董事會 + 法務室 + 關係企業發展 + 稽核室 + 總經理室 @@ -6284,10 +6378,27 @@ export function CompetitionManagement() { + ACBU + AUBU + FAB3 + FNBU HQBU + HRBU + IBU + ICDU + ICBU ITBU MBU1 + MBU5 + PJA + PBU + SBG SBU + 董事會 + 法務室 + 關係企業發展 + 稽核室 + 總經理室 @@ -6698,7 +6809,7 @@ export function CompetitionManagement() { 部門 * {/* 自定義部門輸入框 */} - {(newJudge.department === "" || !["HQBU", "ITBU", "MBU1", "MBU2", "SBU", "研發部", "產品部", "技術部"].includes(newJudge.department)) && ( + {(newJudge.department === "" || !["ACBU", "AUBU", "FAB3", "FNBU", "HQBU", "HRBU", "IBU", "ICDU", "ICBU", "ITBU", "MBU1", "MBU5", "PJA", "PBU", "SBG", "SBU", "董事會", "法務室", "關係企業發展", "稽核室", "總經理室"].includes(newJudge.department)) && ( setNewJudge({ ...newJudge, department: e.target.value })} diff --git a/components/admin/team-management.tsx b/components/admin/team-management.tsx index 889caea..b554396 100644 --- a/components/admin/team-management.tsx +++ b/components/admin/team-management.tsx @@ -308,10 +308,27 @@ export function TeamManagement() { 全部部門 + ACBU + AUBU + FAB3 + FNBU HQBU + HRBU + IBU + ICDU + ICBU ITBU MBU1 + MBU5 + PJA + PBU + SBG SBU + 董事會 + 法務室 + 關係企業發展 + 稽核室 + 總經理室 diff --git a/components/admin/user-management.tsx b/components/admin/user-management.tsx index f7560f5..6943e0a 100644 --- a/components/admin/user-management.tsx +++ b/components/admin/user-management.tsx @@ -806,10 +806,27 @@ export function UserManagement() { 全部部門 + ACBU + AUBU + FAB3 + FNBU HQBU + HRBU + IBU + ICDU + ICBU ITBU MBU1 + MBU5 + PJA + PBU + SBG SBU + 董事會 + 法務室 + 關係企業發展 + 稽核室 + 總經理室 @@ -1183,10 +1200,27 @@ export function UserManagement() { + ACBU + AUBU + FAB3 + FNBU HQBU + HRBU + IBU + ICDU + ICBU ITBU MBU1 + MBU5 + PJA + PBU + SBG SBU + 董事會 + 法務室 + 關係企業發展 + 稽核室 + 總經理室 diff --git a/components/app-detail-dialog.tsx b/components/app-detail-dialog.tsx index 5f96542..aae243e 100644 --- a/components/app-detail-dialog.tsx +++ b/components/app-detail-dialog.tsx @@ -44,6 +44,11 @@ import { Database, Palette, Volume2, + Camera, + Smartphone, + Monitor, + Globe, + FileText, } from "lucide-react" import { FavoriteButton } from "./favorite-button" import { ReviewSystem } from "./reviews/review-system" @@ -150,9 +155,14 @@ export function AppDetailDialog({ open, onOpenChange, app }: AppDetailDialogProp 'ChevronLeft': ChevronLeft, 'ChevronRight': ChevronRight, 'ArrowLeft': ArrowLeft, - 'Settings': Settings + 'Settings': Settings, + 'Camera': Camera, + 'Smartphone': Smartphone, + 'Monitor': Monitor, + 'Globe': Globe, + 'FileText': FileText, } - return iconMap[iconName] || Brain // 預設使用 Brain 圖標 + return iconMap[iconName] || Bot // 預設使用 Bot 圖標 } const IconComponent = getIconComponent(app.icon || 'Bot') diff --git a/components/auth/register-dialog.tsx b/components/auth/register-dialog.tsx index 6af7ad9..02de2b9 100644 --- a/components/auth/register-dialog.tsx +++ b/components/auth/register-dialog.tsx @@ -32,7 +32,7 @@ export function RegisterDialog({ open, onOpenChange }: RegisterDialogProps) { const [showConfirmPassword, setShowConfirmPassword] = useState(false) const [success, setSuccess] = useState(false) - const departments = ["HQBU", "ITBU", "MBU1", "MBU2", "SBU", "財務部", "人資部", "法務部"] + const departments = ["ACBU", "AUBU", "FAB3", "FNBU", "HQBU", "HRBU", "IBU", "ICDU", "ICBU", "ITBU", "MBU1", "MBU5", "PJA", "PBU", "SBG", "SBU", "董事會", "法務室", "關係企業發展", "稽核室", "總經理室"] const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() diff --git a/components/competition/popularity-rankings.tsx b/components/competition/popularity-rankings.tsx index 5ca9f8d..03e3851 100644 --- a/components/competition/popularity-rankings.tsx +++ b/components/competition/popularity-rankings.tsx @@ -748,10 +748,27 @@ export function PopularityRankings() { 全部部門 + ACBU + AUBU + FAB3 + FNBU HQBU + HRBU + IBU + ICDU + ICBU ITBU MBU1 + MBU5 + PJA + PBU + SBG SBU + 董事會 + 法務室 + 關係企業發展 + 稽核室 + 總經理室 diff --git a/components/competition/registration-dialog.tsx b/components/competition/registration-dialog.tsx index b236c6b..93b0a2c 100644 --- a/components/competition/registration-dialog.tsx +++ b/components/competition/registration-dialog.tsx @@ -319,10 +319,27 @@ export function RegistrationDialog({ open, onOpenChange }: RegistrationDialogPro + ACBU + AUBU + FAB3 + FNBU HQBU + HRBU + IBU + ICDU + ICBU ITBU MBU1 + MBU5 + PJA + PBU + SBG SBU + 董事會 + 法務室 + 關係企業發展 + 稽核室 + 總經理室 其他 diff --git a/components/competition/team-detail-dialog.tsx b/components/competition/team-detail-dialog.tsx index abcb3f2..117ec15 100644 --- a/components/competition/team-detail-dialog.tsx +++ b/components/competition/team-detail-dialog.tsx @@ -27,6 +27,11 @@ import { Volume2, Search, BarChart3, + Camera, + Smartphone, + Monitor, + Globe, + FileText, } from "lucide-react" import { useAuth } from "@/contexts/auth-context" import { LikeButton } from "@/components/like-button" @@ -54,9 +59,14 @@ const getIconComponent = (iconName: string) => { 'MessageSquare': MessageSquare, 'Zap': Zap, 'TrendingUp': TrendingUp, + 'Camera': Camera, + 'Smartphone': Smartphone, + 'Monitor': Monitor, + 'Globe': Globe, + 'FileText': FileText, }; - return iconMap[iconName] || Brain; + return iconMap[iconName] || Bot; } // App data for team apps - get from team data diff --git a/components/favorites-page.tsx b/components/favorites-page.tsx index 1258f83..b3151ae 100644 --- a/components/favorites-page.tsx +++ b/components/favorites-page.tsx @@ -6,7 +6,7 @@ import { Card, CardContent } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" -import { Heart, ExternalLink, Star, Eye, ThumbsUp, MessageSquare, Brain, ImageIcon, Mic, MessageSquare as MessageSquareIcon, Settings, Zap, TrendingUp, Target, Users, Lightbulb, Search } from "lucide-react" +import { Heart, ExternalLink, Star, Eye, ThumbsUp, MessageSquare, Brain, ImageIcon, Mic, MessageSquare as MessageSquareIcon, Settings, Zap, TrendingUp, Target, Users, Lightbulb, Search, Database, BarChart3, Camera, Smartphone, Monitor, Globe, FileText, Bot } from "lucide-react" export function FavoritesPage() { const { user } = useAuth() @@ -116,7 +116,8 @@ export function FavoritesPage() { // 圖標映射函數 const getIconComponent = (iconName: string) => { const iconMap: { [key: string]: any } = { - 'Bot': Brain, + 'Bot': Bot, + 'Brain': Brain, 'ImageIcon': ImageIcon, 'Mic': Mic, 'MessageSquare': MessageSquareIcon, @@ -130,8 +131,15 @@ export function FavoritesPage() { 'Users': Users, 'Lightbulb': Lightbulb, 'Search': Search, + 'Database': Database, + 'BarChart3': BarChart3, + 'Camera': Camera, + 'Smartphone': Smartphone, + 'Monitor': Monitor, + 'Globe': Globe, + 'FileText': FileText, } - return iconMap[iconName] || Heart + return iconMap[iconName] || Bot } const getTypeColor = (type: string) => { @@ -187,7 +195,27 @@ export function FavoritesPage() { 所有部門 + ACBU + AUBU + FAB3 + FNBU + HQBU + HRBU + IBU + ICDU + ICBU ITBU + MBU1 + MBU5 + PJA + PBU + SBG + SBU + 董事會 + 法務室 + 關係企業發展 + 稽核室 + 總經理室 HR Finance