建立檔案
This commit is contained in:
325
components/competition/team-detail-dialog.tsx
Normal file
325
components/competition/team-detail-dialog.tsx
Normal file
@@ -0,0 +1,325 @@
|
||||
"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,
|
||||
} 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
|
||||
}
|
||||
|
||||
// App data for team apps - empty for production
|
||||
const getAppDetails = (appId: string) => {
|
||||
return {
|
||||
id: appId,
|
||||
name: "",
|
||||
type: "",
|
||||
description: "",
|
||||
icon: null,
|
||||
fullDescription: "",
|
||||
features: [],
|
||||
author: "",
|
||||
category: "",
|
||||
tags: [],
|
||||
demoUrl: "",
|
||||
sourceUrl: "",
|
||||
}
|
||||
}
|
||||
|
||||
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.id === team.leader)
|
||||
|
||||
const handleAppClick = (appId: string) => {
|
||||
const appDetails = getAppDetails(appId)
|
||||
// Create app object that matches AppDetailDialog interface
|
||||
const app = {
|
||||
id: Number.parseInt(appId),
|
||||
name: appDetails.name,
|
||||
type: appDetails.type,
|
||||
department: team.department, // Use team's department
|
||||
description: appDetails.description,
|
||||
icon: appDetails.icon,
|
||||
creator: appDetails.author,
|
||||
featured: false,
|
||||
judgeScore: 0,
|
||||
}
|
||||
setSelectedApp(app)
|
||||
setAppDetailOpen(true)
|
||||
}
|
||||
|
||||
const totalLikes = team.apps.reduce((sum: number, appId: string) => sum + getLikeCount(appId), 0)
|
||||
const totalViews = team.apps.reduce((sum: number, appId: string) => sum + getViewCount(appId), 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.contactEmail}</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)
|
||||
const IconComponent = app.icon
|
||||
const likes = getLikeCount(appId)
|
||||
const views = getViewCount(appId)
|
||||
const rating = getAppRating(appId)
|
||||
|
||||
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>{rating.toFixed(1)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<LikeButton appId={appId} size="sm" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* App Detail Dialog */}
|
||||
{selectedApp && <AppDetailDialog open={appDetailOpen} onOpenChange={setAppDetailOpen} app={selectedApp} />}
|
||||
</>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user