修正團體管理的 BUG
This commit is contained in:
@@ -517,7 +517,8 @@ export function AppManagement() {
|
||||
type: newApp.type,
|
||||
app_url: newApp.appUrl,
|
||||
icon: newApp.icon,
|
||||
icon_color: newApp.iconColor
|
||||
icon_color: newApp.iconColor,
|
||||
department: newApp.department
|
||||
})
|
||||
})
|
||||
|
||||
|
@@ -563,7 +563,13 @@ export function CompetitionManagement() {
|
||||
contactEmail: teamDetails.contact_email || '',
|
||||
leaderPhone: teamDetails.leader_phone || '',
|
||||
description: teamDetails.description || '',
|
||||
members: teamDetails.members || [],
|
||||
members: (teamDetails.members || []).map((member: any) => ({
|
||||
id: member.user_id || member.id,
|
||||
user_id: member.user_id || member.id,
|
||||
name: member.name,
|
||||
department: member.department,
|
||||
role: member.role || '成員'
|
||||
})),
|
||||
apps: teamDetails.apps ? teamDetails.apps.map((app: any) => app.id || app) : [],
|
||||
submittedAppCount: teamDetails.apps?.length || 0,
|
||||
}
|
||||
@@ -1142,7 +1148,7 @@ export function CompetitionManagement() {
|
||||
contact_email: newTeam.contactEmail,
|
||||
description: newTeam.description,
|
||||
members: newTeam.members.map(member => ({
|
||||
user_id: member.id, // 現在 member.id 就是 user_id
|
||||
user_id: member.user_id || member.id, // 確保使用正確的 user_id
|
||||
role: member.role || 'member'
|
||||
})),
|
||||
apps: newTeam.apps // 添加應用 ID 列表
|
||||
@@ -1164,7 +1170,7 @@ export function CompetitionManagement() {
|
||||
contact_email: newTeam.contactEmail,
|
||||
description: newTeam.description,
|
||||
members: newTeam.members.map(member => ({
|
||||
user_id: member.id, // 現在 member.id 就是 user_id
|
||||
user_id: member.user_id || member.id, // 確保使用正確的 user_id
|
||||
role: member.role || 'member'
|
||||
})),
|
||||
apps: newTeam.apps // 添加應用 ID 列表
|
||||
@@ -2136,6 +2142,19 @@ export function CompetitionManagement() {
|
||||
}
|
||||
}
|
||||
|
||||
const getCompetitionTypeColor = (type: string) => {
|
||||
switch (type) {
|
||||
case "individual":
|
||||
return "bg-blue-100 text-blue-800 border-blue-200"
|
||||
case "team":
|
||||
return "bg-purple-100 text-purple-800 border-purple-200"
|
||||
case "mixed":
|
||||
return "bg-orange-100 text-orange-800 border-orange-200"
|
||||
default:
|
||||
return "bg-gray-100 text-gray-800 border-gray-200"
|
||||
}
|
||||
}
|
||||
|
||||
const getScoreLabelText = (key: string) => {
|
||||
switch (key) {
|
||||
case "innovation":
|
||||
@@ -2464,13 +2483,13 @@ export function CompetitionManagement() {
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>競賽名稱</TableHead>
|
||||
<TableHead>類型</TableHead>
|
||||
<TableHead>時間</TableHead>
|
||||
<TableHead>狀態</TableHead>
|
||||
<TableHead>參賽項目</TableHead>
|
||||
<TableHead>評分進度</TableHead>
|
||||
<TableHead>操作</TableHead>
|
||||
<TableHead className="w-[300px]">競賽名稱</TableHead>
|
||||
<TableHead className="w-[120px]">類型</TableHead>
|
||||
<TableHead className="w-[150px]">時間</TableHead>
|
||||
<TableHead className="w-[100px]">狀態</TableHead>
|
||||
<TableHead className="w-[120px]">參賽項目</TableHead>
|
||||
<TableHead className="w-[150px]">評分進度</TableHead>
|
||||
<TableHead className="w-[80px]">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -2506,9 +2525,9 @@ export function CompetitionManagement() {
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center space-x-2 min-w-[100px]">
|
||||
{getCompetitionTypeIcon(competition.type)}
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<Badge variant="outline" className={`text-xs px-2 py-1 whitespace-nowrap ${getCompetitionTypeColor(competition.type)}`}>
|
||||
{getCompetitionTypeText(competition.type)}
|
||||
</Badge>
|
||||
</div>
|
||||
@@ -2527,34 +2546,37 @@ export function CompetitionManagement() {
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline" className={getStatusColor(competition.status)}>
|
||||
{getStatusText(competition.status)}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center space-x-1">
|
||||
{getCompetitionTypeIcon(competition.type)}
|
||||
<span>{participantCount} 個</span>
|
||||
<div className="min-w-[80px]">
|
||||
<Badge variant="outline" className={`text-xs px-2 py-1 whitespace-nowrap ${getStatusColor(competition.status)}`}>
|
||||
{getStatusText(competition.status)}
|
||||
</Badge>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center space-x-1 min-w-[100px]">
|
||||
{getCompetitionTypeIcon(competition.type)}
|
||||
<span className="text-sm whitespace-nowrap">{participantCount} 個</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="space-y-1 min-w-[140px]">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Progress value={scoringProgress.percentage} className="w-16 h-2" />
|
||||
<span className="text-xs text-gray-500">
|
||||
<span className="text-xs text-gray-500 whitespace-nowrap">
|
||||
{scoringProgress.completed}/{scoringProgress.total}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500">{scoringProgress.percentage}% 完成</p>
|
||||
<p className="text-xs text-gray-500 whitespace-nowrap">{scoringProgress.percentage}% 完成</p>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="sm" disabled={isLoading}>
|
||||
<MoreHorizontal className="w-4 h-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<div className="flex justify-center min-w-[60px]">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="sm" disabled={isLoading}>
|
||||
<MoreHorizontal className="w-4 h-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => handleViewCompetition(competition)}>
|
||||
<Eye className="w-4 h-4 mr-2" />
|
||||
@@ -2598,7 +2620,8 @@ export function CompetitionManagement() {
|
||||
刪除競賽
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
@@ -2771,7 +2794,7 @@ export function CompetitionManagement() {
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 text-gray-600">
|
||||
<Trophy className="w-3 h-3" />
|
||||
<span>{team.submittedAppCount} 個應用</span>
|
||||
<span>{team.app_count || 0} 個應用</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6603,7 +6626,7 @@ export function CompetitionManagement() {
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center space-x-2">
|
||||
<p className="font-medium text-gray-900">{member.name}</p>
|
||||
{member.user_id === selectedTeam.leader_id && (
|
||||
{member.role === '隊長' && (
|
||||
<Badge variant="default" className="text-xs bg-orange-100 text-orange-800">
|
||||
隊長
|
||||
</Badge>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useState, useEffect } from "react"
|
||||
import { useCompetition } from "@/contexts/competition-context"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
@@ -43,6 +43,10 @@ export function TeamManagement() {
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [success, setSuccess] = useState("")
|
||||
const [error, setError] = useState("")
|
||||
|
||||
// 從 API 獲取的團隊數據
|
||||
const [apiTeams, setApiTeams] = useState<any[]>([])
|
||||
const [isLoadingTeams, setIsLoadingTeams] = useState(true)
|
||||
|
||||
const [newTeam, setNewTeam] = useState({
|
||||
name: "",
|
||||
@@ -60,10 +64,35 @@ export function TeamManagement() {
|
||||
role: "成員",
|
||||
})
|
||||
|
||||
const filteredTeams = teams.filter((team) => {
|
||||
// 獲取團隊數據
|
||||
const fetchTeams = async () => {
|
||||
try {
|
||||
setIsLoadingTeams(true)
|
||||
const response = await fetch('/api/admin/teams')
|
||||
const data = await response.json()
|
||||
|
||||
if (data.success) {
|
||||
setApiTeams(data.data)
|
||||
} else {
|
||||
console.error('獲取團隊數據失敗:', data.message)
|
||||
setError('獲取團隊數據失敗')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('獲取團隊數據失敗:', error)
|
||||
setError('獲取團隊數據失敗')
|
||||
} finally {
|
||||
setIsLoadingTeams(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchTeams()
|
||||
}, [])
|
||||
|
||||
const filteredTeams = apiTeams.filter((team) => {
|
||||
const matchesSearch =
|
||||
team.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
team.members.some((member) => member.name.toLowerCase().includes(searchTerm.toLowerCase()))
|
||||
team.leader_name?.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
const matchesDepartment = selectedDepartment === "all" || team.department === selectedDepartment
|
||||
return matchesSearch && matchesDepartment
|
||||
})
|
||||
@@ -73,16 +102,29 @@ export function TeamManagement() {
|
||||
setShowTeamDetail(true)
|
||||
}
|
||||
|
||||
const handleEditTeam = (team: Team) => {
|
||||
const handleEditTeam = (team: any) => {
|
||||
setSelectedTeam(team)
|
||||
|
||||
// 確保成員數據結構正確
|
||||
const members = team.members && Array.isArray(team.members)
|
||||
? team.members.map((member: any) => ({
|
||||
id: member.user_id || member.id,
|
||||
user_id: member.user_id || member.id,
|
||||
name: member.name,
|
||||
department: member.department,
|
||||
role: member.role || '成員'
|
||||
}))
|
||||
: []
|
||||
|
||||
setNewTeam({
|
||||
name: team.name,
|
||||
department: team.department,
|
||||
contactEmail: team.contactEmail,
|
||||
members: [...team.members],
|
||||
leader: team.leader,
|
||||
apps: [...team.apps],
|
||||
totalLikes: team.totalLikes,
|
||||
contactEmail: team.contact_email || team.contactEmail,
|
||||
description: team.description || '',
|
||||
members: members,
|
||||
leader: team.leader_id || team.leader,
|
||||
apps: team.apps || [],
|
||||
totalLikes: team.total_likes || team.totalLikes || 0,
|
||||
})
|
||||
setShowEditTeam(true)
|
||||
}
|
||||
@@ -205,14 +247,49 @@ export function TeamManagement() {
|
||||
}
|
||||
|
||||
setIsLoading(true)
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
|
||||
updateTeam(selectedTeam.id, newTeam)
|
||||
setShowEditTeam(false)
|
||||
setSelectedTeam(null)
|
||||
setSuccess("團隊更新成功!")
|
||||
setIsLoading(false)
|
||||
setTimeout(() => setSuccess(""), 3000)
|
||||
try {
|
||||
// 準備更新數據
|
||||
const updateData = {
|
||||
name: newTeam.name,
|
||||
department: newTeam.department,
|
||||
contact_email: newTeam.contactEmail,
|
||||
description: newTeam.description || null,
|
||||
leader_id: newTeam.leader,
|
||||
members: newTeam.members.map(member => ({
|
||||
user_id: member.user_id || member.id,
|
||||
role: member.role
|
||||
}))
|
||||
}
|
||||
|
||||
// 調用 API 更新團隊
|
||||
const response = await fetch(`/api/admin/teams/${selectedTeam.id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(updateData)
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (data.success) {
|
||||
// 更新成功,重新載入團隊數據
|
||||
await fetchTeams()
|
||||
setShowEditTeam(false)
|
||||
setSelectedTeam(null)
|
||||
setSuccess("團隊更新成功!")
|
||||
} else {
|
||||
setError(data.message || "更新團隊失敗")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新團隊失敗:', error)
|
||||
setError("更新團隊失敗")
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
setTimeout(() => setError(""), 3000)
|
||||
setTimeout(() => setSuccess(""), 3000)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -254,7 +331,7 @@ export function TeamManagement() {
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-gray-600">總團隊數</p>
|
||||
<p className="text-2xl font-bold">{teams.length}</p>
|
||||
<p className="text-2xl font-bold">{apiTeams.length}</p>
|
||||
</div>
|
||||
<Users className="w-8 h-8 text-green-600" />
|
||||
</div>
|
||||
@@ -266,7 +343,7 @@ export function TeamManagement() {
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-gray-600">總成員數</p>
|
||||
<p className="text-2xl font-bold">{teams.reduce((sum, team) => sum + team.members.length, 0)}</p>
|
||||
<p className="text-2xl font-bold">{apiTeams.reduce((sum, team) => sum + (team.member_count || 0), 0)}</p>
|
||||
</div>
|
||||
<UserPlus className="w-8 h-8 text-blue-600" />
|
||||
</div>
|
||||
@@ -279,8 +356,8 @@ export function TeamManagement() {
|
||||
<div>
|
||||
<p className="text-sm text-gray-600">平均團隊規模</p>
|
||||
<p className="text-2xl font-bold">
|
||||
{teams.length > 0
|
||||
? Math.round((teams.reduce((sum, team) => sum + team.members.length, 0) / teams.length) * 10) / 10
|
||||
{apiTeams.length > 0
|
||||
? Math.round((apiTeams.reduce((sum, team) => sum + (team.member_count || 0), 0) / apiTeams.length) * 10) / 10
|
||||
: 0}
|
||||
</p>
|
||||
</div>
|
||||
@@ -352,8 +429,6 @@ export function TeamManagement() {
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredTeams.map((team) => {
|
||||
const leader = team.members.find((m) => m.id === team.leader)
|
||||
|
||||
return (
|
||||
<TableRow key={team.id}>
|
||||
<TableCell>
|
||||
@@ -363,7 +438,7 @@ export function TeamManagement() {
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium">{team.name}</p>
|
||||
<p className="text-sm text-gray-500">{team.contactEmail}</p>
|
||||
<p className="text-sm text-gray-500">{team.contact_email}</p>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
@@ -371,10 +446,10 @@ export function TeamManagement() {
|
||||
<div className="flex items-center space-x-2">
|
||||
<Avatar className="w-6 h-6">
|
||||
<AvatarFallback className="bg-green-100 text-green-700 text-xs">
|
||||
{leader?.name[0] || "?"}
|
||||
{team.leader_name?.[0] || "?"}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<span className="text-sm">{leader?.name || "未設定"}</span>
|
||||
<span className="text-sm">{team.leader_name || "未設定"}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -385,11 +460,11 @@ export function TeamManagement() {
|
||||
<TableCell>
|
||||
<div className="flex items-center space-x-1">
|
||||
<UserPlus className="w-4 h-4 text-blue-500" />
|
||||
<span>{team.members.length}</span>
|
||||
<span>{team.member_count || 0}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="font-medium">{team.apps.length}</span>
|
||||
<span className="font-medium">{team.app_count || 0}</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="font-medium text-red-600">{team.totalLikes}</span>
|
||||
@@ -807,16 +882,16 @@ export function TeamManagement() {
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-xl font-semibold">{selectedTeam.name}</h3>
|
||||
<p className="text-gray-600 mb-2">{selectedTeam.contactEmail}</p>
|
||||
<p className="text-gray-600 mb-2">{selectedTeam.contact_email}</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Badge variant="outline" className="bg-gray-100 text-gray-700">
|
||||
{selectedTeam.department}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="bg-blue-100 text-blue-700">
|
||||
{selectedTeam.members.length} 名成員
|
||||
{selectedTeam.member_count || 0} 名成員
|
||||
</Badge>
|
||||
<Badge variant="outline" className="bg-green-100 text-green-700">
|
||||
{selectedTeam.apps.length} 個應用
|
||||
{selectedTeam.app_count || 0} 個應用
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user