Files
ai-showcase-platform/components/competition/team-detail-dialog.tsx

396 lines
16 KiB
TypeScript
Raw 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 { useState } from "react"
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import {
Users,
Mail,
Eye,
Heart,
Trophy,
Star,
MessageSquare,
ImageIcon,
Mic,
TrendingUp,
Brain,
Zap,
ExternalLink,
Bot,
Code,
Database,
Palette,
Volume2,
Search,
BarChart3,
} from "lucide-react"
import { useAuth } from "@/contexts/auth-context"
import { LikeButton } from "@/components/like-button"
import { AppDetailDialog } from "@/components/app-detail-dialog"
interface TeamDetailDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
team: any
}
// 圖標映射函數
const getIconComponent = (iconName: string) => {
const iconMap: { [key: string]: any } = {
'Brain': Brain,
'Bot': Bot,
'Code': Code,
'Database': Database,
'Palette': Palette,
'Volume2': Volume2,
'Search': Search,
'BarChart3': BarChart3,
'Mic': Mic,
'ImageIcon': ImageIcon,
'MessageSquare': MessageSquare,
'Zap': Zap,
'TrendingUp': TrendingUp,
};
return iconMap[iconName] || Brain;
}
// App data for team apps - get from team data
const getAppDetails = (appId: string, team: any) => {
const appDetail = team.appsDetails?.find((app: any) => app.id === appId);
if (appDetail) {
return {
id: appDetail.id,
name: appDetail.name || "未命名應用",
type: appDetail.type || "未知類型",
description: appDetail.description || "無描述",
icon: getIconComponent(appDetail.icon) || Brain,
iconColor: appDetail.icon_color || "from-blue-500 to-purple-500",
fullDescription: appDetail.description || "無描述",
features: [],
author: appDetail.creator_name || "未知作者",
department: appDetail.creator_department || "未知部門",
category: appDetail.category || "未分類",
tags: [],
demoUrl: "",
sourceUrl: "",
likes: appDetail.likes_count || 0,
views: appDetail.views_count || 0,
rating: Number(appDetail.rating) || 0,
createdAt: appDetail.created_at
};
}
return {
id: appId,
name: "未命名應用",
type: "未知類型",
description: "無描述",
icon: Brain,
fullDescription: "無描述",
features: [],
author: "未知作者",
category: "未分類",
tags: [],
demoUrl: "",
sourceUrl: "",
likes: 0,
views: 0,
rating: 0
};
}
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"
}
export function TeamDetailDialog({ open, onOpenChange, team }: TeamDetailDialogProps) {
const { getLikeCount, getViewCount, getAppRating } = useAuth()
const [selectedTab, setSelectedTab] = useState("overview")
const [selectedApp, setSelectedApp] = useState<any>(null)
const [appDetailOpen, setAppDetailOpen] = useState(false)
if (!team) return null
const leader = team.members.find((m: any) => m.user_id === team.leader)
const handleAppClick = (appId: string) => {
const appDetails = getAppDetails(appId, team)
// Create app object that matches AppDetailDialog interface
const app = {
id: appId, // 保持為字符串API 期望字符串 ID
name: appDetails.name,
type: appDetails.type,
department: appDetails.department, // Use app's creator department
description: appDetails.description,
icon: appDetails.icon,
iconColor: appDetails.iconColor, // Pass icon color
creator: appDetails.author,
featured: false,
judgeScore: appDetails.rating || 0,
// 使用 AppDetailDialog 期望的屬性名
likesCount: appDetails.likes || 0,
viewsCount: appDetails.views || 0,
rating: appDetails.rating || 0,
reviewsCount: 0, // 可以從 API 獲取,暫時設為 0
createdAt: appDetails.createdAt, // Pass creation date
}
setSelectedApp(app)
setAppDetailOpen(true)
}
// 使用從數據庫獲取的真實數據
const totalLikes = team.totalLikes || 0
const totalViews = team.totalViews || 0
return (
<>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="flex items-center space-x-2">
<Users className="w-5 h-5 text-green-500" />
<span>{team.name}</span>
</DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<div className="space-y-6">
{/* Team Overview */}
<Card>
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="text-center p-4 bg-green-50 rounded-lg">
<div className="text-2xl font-bold text-green-600">{team.members.length}</div>
<div className="text-sm text-gray-600"></div>
</div>
<div className="text-center p-4 bg-blue-50 rounded-lg">
<div className="text-2xl font-bold text-blue-600">{team.apps.length}</div>
<div className="text-sm text-gray-600"></div>
</div>
<div className="text-center p-4 bg-red-50 rounded-lg">
<div className="text-2xl font-bold text-red-600">{totalLikes}</div>
<div className="text-sm text-gray-600"></div>
</div>
<div className="text-center p-4 bg-purple-50 rounded-lg">
<div className="text-2xl font-bold text-purple-600">{totalViews}</div>
<div className="text-sm text-gray-600"></div>
</div>
</div>
</CardContent>
</Card>
{/* Tab Navigation */}
<div className="flex space-x-1 bg-gray-100 p-1 rounded-lg">
<Button
variant={selectedTab === "overview" ? "default" : "ghost"}
size="sm"
onClick={() => setSelectedTab("overview")}
className="flex-1"
>
</Button>
<Button
variant={selectedTab === "members" ? "default" : "ghost"}
size="sm"
onClick={() => setSelectedTab("members")}
className="flex-1"
>
</Button>
<Button
variant={selectedTab === "apps" ? "default" : "ghost"}
size="sm"
onClick={() => setSelectedTab("apps")}
className="flex-1"
>
</Button>
</div>
{/* Tab Content */}
{selectedTab === "overview" && (
<div className="space-y-4">
<Card>
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="text-sm font-medium text-gray-700"></label>
<p className="text-gray-900">{team.name}</p>
</div>
<div>
<label className="text-sm font-medium text-gray-700"></label>
<Badge variant="outline" className="ml-2">
{team.department}
</Badge>
</div>
<div>
<label className="text-sm font-medium text-gray-700"></label>
<p className="text-gray-900">{leader?.name || '未指定'}</p>
</div>
<div>
<label className="text-sm font-medium text-gray-700"></label>
<div className="flex items-center space-x-2">
<Mail className="w-4 h-4 text-gray-500" />
<p className="text-gray-900">{team.contact_email || '未提供'}</p>
</div>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="text-center p-4 border rounded-lg">
<Trophy className="w-8 h-8 text-yellow-500 mx-auto mb-2" />
<div className="text-lg font-bold">{team.popularityScore}</div>
<div className="text-sm text-gray-600"></div>
</div>
<div className="text-center p-4 border rounded-lg">
<Eye className="w-8 h-8 text-blue-500 mx-auto mb-2" />
<div className="text-lg font-bold">{totalViews}</div>
<div className="text-sm text-gray-600"></div>
</div>
<div className="text-center p-4 border rounded-lg">
<Heart className="w-8 h-8 text-red-500 mx-auto mb-2" />
<div className="text-lg font-bold">{totalLikes}</div>
<div className="text-sm text-gray-600"></div>
</div>
</div>
</CardContent>
</Card>
</div>
)}
{selectedTab === "members" && (
<Card>
<CardHeader>
<CardTitle className="text-lg"> ({team.members.length})</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{team.members.map((member: any, index: number) => (
<div key={member.id} className="flex items-center space-x-3 p-4 border rounded-lg">
<Avatar className="w-12 h-12">
<AvatarImage src={`/placeholder-40x40.png`} />
<AvatarFallback className="bg-green-100 text-green-700 font-medium">
{member.name[0]}
</AvatarFallback>
</Avatar>
<div className="flex-1">
<div className="flex items-center space-x-2">
<h4 className="font-medium">{member.name}</h4>
{member.id === team.leader && (
<Badge variant="default" className="bg-yellow-100 text-yellow-800 text-xs">
</Badge>
)}
</div>
<p className="text-sm text-gray-600">{member.role}</p>
<p className="text-xs text-gray-500">{member.department}</p>
</div>
</div>
))}
</div>
</CardContent>
</Card>
)}
{selectedTab === "apps" && (
<Card>
<CardHeader>
<CardTitle className="text-lg"> ({team.apps.length})</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{team.apps.map((appId: string) => {
const app = getAppDetails(appId, team)
// 如果沒有圖標,使用默認的 Brain 圖標
const IconComponent = app.icon || Brain
const likes = app.likes || 0
const views = app.views || 0
const rating = app.rating || 0
return (
<Card
key={appId}
className="hover:shadow-md transition-all duration-200 cursor-pointer group"
onClick={() => handleAppClick(appId)}
>
<CardContent className="p-4 flex flex-col h-full">
<div className="flex items-start space-x-3 mb-3">
<div className="w-10 h-10 bg-gray-100 rounded-lg flex items-center justify-center group-hover:bg-blue-100 transition-colors">
<IconComponent className="w-5 h-5 text-gray-600 group-hover:text-blue-600" />
</div>
<div className="flex-1">
<div className="flex items-center space-x-2">
<h4 className="font-medium text-gray-900 group-hover:text-blue-600 transition-colors">
{app.name}
</h4>
<ExternalLink className="w-3 h-3 text-gray-400 group-hover:text-blue-500 opacity-0 group-hover:opacity-100 transition-all" />
</div>
<Badge variant="outline" className={`${getTypeColor(app.type)} text-xs mt-1`}>
{app.type}
</Badge>
</div>
</div>
<p className="text-sm text-gray-600 mb-3 line-clamp-2">{app.description}</p>
<div className="flex items-center justify-between mt-auto">
<div className="flex items-center space-x-3 text-xs text-gray-500">
<div className="flex items-center space-x-1">
<Eye className="w-3 h-3" />
<span>{views}</span>
</div>
<div className="flex items-center space-x-1">
<Star className="w-3 h-3 text-yellow-500" />
<span>{Number(rating).toFixed(1)}</span>
</div>
</div>
<div onClick={(e) => e.stopPropagation()}>
<LikeButton
appId={appId}
size="sm"
likeCount={likes}
showCount={true}
/>
</div>
</div>
</CardContent>
</Card>
)
})}
</div>
</CardContent>
</Card>
)}
</div>
</DialogContent>
</Dialog>
{/* App Detail Dialog */}
{selectedApp && <AppDetailDialog open={appDetailOpen} onOpenChange={setAppDetailOpen} app={selectedApp} />}
</>
)
}