修正獎勵專區評審團資料異常 bug
This commit is contained in:
@@ -2143,11 +2143,17 @@ export function CompetitionManagement() {
|
|||||||
|
|
||||||
if (teamsData.success && teamsData.data && teamsData.data.teams) {
|
if (teamsData.success && teamsData.data && teamsData.data.teams) {
|
||||||
console.log('✅ 競賽團隊載入成功:', teamsData.data.teams.length, '個團隊')
|
console.log('✅ 競賽團隊載入成功:', teamsData.data.teams.length, '個團隊')
|
||||||
updatedCompetition.teams = teamsData.data.teams
|
// 確保團隊數據的唯一性,避免重複的ID
|
||||||
// 同時更新dbTeams
|
const uniqueTeams = teamsData.data.teams.filter((team: any, index: number, self: any[]) =>
|
||||||
|
self.findIndex(t => t.id === team.id) === index
|
||||||
|
)
|
||||||
|
updatedCompetition.teams = uniqueTeams
|
||||||
|
console.log('🔍 去重後的團隊數量:', uniqueTeams.length)
|
||||||
|
|
||||||
|
// 同時更新dbTeams,確保不重複添加
|
||||||
setDbTeams(prev => {
|
setDbTeams(prev => {
|
||||||
const existingIds = prev.map(t => t.id)
|
const existingIds = prev.map(t => t.id)
|
||||||
const newTeams = teamsData.data.teams.filter((t: any) => !existingIds.includes(t.id))
|
const newTeams = uniqueTeams.filter((t: any) => !existingIds.includes(t.id))
|
||||||
return [...prev, ...newTeams]
|
return [...prev, ...newTeams]
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -3195,8 +3201,8 @@ export function CompetitionManagement() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return paginatedTeams.map((team) => (
|
return paginatedTeams.map((team, index) => (
|
||||||
<Card key={team.id} className="relative h-fit">
|
<Card key={`${team.id}-${index}`} className="relative h-fit">
|
||||||
<CardContent className="p-4">
|
<CardContent className="p-4">
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
@@ -4067,7 +4073,7 @@ export function CompetitionManagement() {
|
|||||||
if (links && links.production) {
|
if (links && links.production) {
|
||||||
linkItems.push(
|
linkItems.push(
|
||||||
<div key="production" className="flex items-center space-x-1">
|
<div key="production" className="flex items-center space-x-1">
|
||||||
<div className="w-2 h-2 bg-green-500 rounded-full" title="生產環境"></div>
|
<div className="w-2 h-2 bg-green-500 rounded-full" title="APP 連結"></div>
|
||||||
<a
|
<a
|
||||||
href={links.production}
|
href={links.production}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -4075,7 +4081,7 @@ export function CompetitionManagement() {
|
|||||||
className="text-xs text-blue-600 hover:text-blue-800 underline truncate max-w-24"
|
className="text-xs text-blue-600 hover:text-blue-800 underline truncate max-w-24"
|
||||||
title={links.production}
|
title={links.production}
|
||||||
>
|
>
|
||||||
生產環境
|
APP 連結
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -4538,7 +4544,7 @@ export function CompetitionManagement() {
|
|||||||
<div className="col-span-4">
|
<div className="col-span-4">
|
||||||
<Label className="text-xs">評比項目名稱</Label>
|
<Label className="text-xs">評比項目名稱</Label>
|
||||||
<Input
|
<Input
|
||||||
value={rule.name || ""}
|
value={rule.name ?? ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const updatedRules = [...newCompetition.individualConfig.rules]
|
const updatedRules = [...newCompetition.individualConfig.rules]
|
||||||
updatedRules[index] = { ...rule, name: e.target.value }
|
updatedRules[index] = { ...rule, name: e.target.value }
|
||||||
@@ -4557,7 +4563,7 @@ export function CompetitionManagement() {
|
|||||||
<div className="col-span-5">
|
<div className="col-span-5">
|
||||||
<Label className="text-xs">描述</Label>
|
<Label className="text-xs">描述</Label>
|
||||||
<Input
|
<Input
|
||||||
value={rule.description || ""}
|
value={rule.description ?? ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const updatedRules = [...newCompetition.individualConfig.rules]
|
const updatedRules = [...newCompetition.individualConfig.rules]
|
||||||
updatedRules[index] = { ...rule, description: e.target.value }
|
updatedRules[index] = { ...rule, description: e.target.value }
|
||||||
@@ -4579,7 +4585,7 @@ export function CompetitionManagement() {
|
|||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
max="100"
|
max="100"
|
||||||
value={rule.weight || 0}
|
value={rule.weight ?? 0}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const updatedRules = [...newCompetition.individualConfig.rules]
|
const updatedRules = [...newCompetition.individualConfig.rules]
|
||||||
updatedRules[index] = { ...rule, weight: Number.parseInt(e.target.value) || 0 }
|
updatedRules[index] = { ...rule, weight: Number.parseInt(e.target.value) || 0 }
|
||||||
@@ -4672,7 +4678,7 @@ export function CompetitionManagement() {
|
|||||||
<div className="col-span-1">
|
<div className="col-span-1">
|
||||||
<Label className="text-xs">圖示</Label>
|
<Label className="text-xs">圖示</Label>
|
||||||
<Select
|
<Select
|
||||||
value={awardType.icon || "🏆"}
|
value={awardType.icon ?? "🏆"}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
const updatedAwardTypes = [...newCompetition.individualConfig.awardTypes]
|
const updatedAwardTypes = [...newCompetition.individualConfig.awardTypes]
|
||||||
updatedAwardTypes[index] = { ...awardType, icon: value }
|
updatedAwardTypes[index] = { ...awardType, icon: value }
|
||||||
@@ -4706,7 +4712,7 @@ export function CompetitionManagement() {
|
|||||||
<div className="col-span-3">
|
<div className="col-span-3">
|
||||||
<Label className="text-xs">獎項名稱</Label>
|
<Label className="text-xs">獎項名稱</Label>
|
||||||
<Input
|
<Input
|
||||||
value={awardType.name || ""}
|
value={awardType.name ?? ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const updatedAwardTypes = [...newCompetition.individualConfig.awardTypes]
|
const updatedAwardTypes = [...newCompetition.individualConfig.awardTypes]
|
||||||
updatedAwardTypes[index] = { ...awardType, name: e.target.value }
|
updatedAwardTypes[index] = { ...awardType, name: e.target.value }
|
||||||
@@ -4725,7 +4731,7 @@ export function CompetitionManagement() {
|
|||||||
<div className="col-span-5">
|
<div className="col-span-5">
|
||||||
<Label className="text-xs">獎項描述</Label>
|
<Label className="text-xs">獎項描述</Label>
|
||||||
<Input
|
<Input
|
||||||
value={awardType.description || ""}
|
value={awardType.description ?? ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const updatedAwardTypes = [...newCompetition.individualConfig.awardTypes]
|
const updatedAwardTypes = [...newCompetition.individualConfig.awardTypes]
|
||||||
updatedAwardTypes[index] = { ...awardType, description: e.target.value }
|
updatedAwardTypes[index] = { ...awardType, description: e.target.value }
|
||||||
@@ -4744,7 +4750,7 @@ export function CompetitionManagement() {
|
|||||||
<div className="col-span-2">
|
<div className="col-span-2">
|
||||||
<Label className="text-xs">顏色主題</Label>
|
<Label className="text-xs">顏色主題</Label>
|
||||||
<Select
|
<Select
|
||||||
value={awardType.color || "text-yellow-600"}
|
value={awardType.color ?? "text-yellow-600"}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
const updatedAwardTypes = [...newCompetition.individualConfig.awardTypes]
|
const updatedAwardTypes = [...newCompetition.individualConfig.awardTypes]
|
||||||
updatedAwardTypes[index] = { ...awardType, color: value }
|
updatedAwardTypes[index] = { ...awardType, color: value }
|
||||||
@@ -4950,7 +4956,7 @@ export function CompetitionManagement() {
|
|||||||
<div className="col-span-4">
|
<div className="col-span-4">
|
||||||
<Label className="text-xs">評比項目名稱</Label>
|
<Label className="text-xs">評比項目名稱</Label>
|
||||||
<Input
|
<Input
|
||||||
value={rule.name || ""}
|
value={rule.name ?? ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const updatedRules = [...newCompetition.teamConfig.rules]
|
const updatedRules = [...newCompetition.teamConfig.rules]
|
||||||
updatedRules[index] = { ...rule, name: e.target.value }
|
updatedRules[index] = { ...rule, name: e.target.value }
|
||||||
@@ -4969,7 +4975,7 @@ export function CompetitionManagement() {
|
|||||||
<div className="col-span-5">
|
<div className="col-span-5">
|
||||||
<Label className="text-xs">描述</Label>
|
<Label className="text-xs">描述</Label>
|
||||||
<Input
|
<Input
|
||||||
value={rule.description || ""}
|
value={rule.description ?? ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const updatedRules = [...newCompetition.teamConfig.rules]
|
const updatedRules = [...newCompetition.teamConfig.rules]
|
||||||
updatedRules[index] = { ...rule, description: e.target.value }
|
updatedRules[index] = { ...rule, description: e.target.value }
|
||||||
@@ -4991,7 +4997,7 @@ export function CompetitionManagement() {
|
|||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
max="100"
|
max="100"
|
||||||
value={rule.weight || 0}
|
value={rule.weight ?? 0}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const updatedRules = [...newCompetition.teamConfig.rules]
|
const updatedRules = [...newCompetition.teamConfig.rules]
|
||||||
updatedRules[index] = { ...rule, weight: Number.parseInt(e.target.value) || 0 }
|
updatedRules[index] = { ...rule, weight: Number.parseInt(e.target.value) || 0 }
|
||||||
@@ -5084,7 +5090,7 @@ export function CompetitionManagement() {
|
|||||||
<div className="col-span-1">
|
<div className="col-span-1">
|
||||||
<Label className="text-xs">圖示</Label>
|
<Label className="text-xs">圖示</Label>
|
||||||
<Select
|
<Select
|
||||||
value={awardType.icon || "🏆"}
|
value={awardType.icon ?? "🏆"}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
const updatedAwardTypes = [...newCompetition.teamConfig.awardTypes]
|
const updatedAwardTypes = [...newCompetition.teamConfig.awardTypes]
|
||||||
updatedAwardTypes[index] = { ...awardType, icon: value }
|
updatedAwardTypes[index] = { ...awardType, icon: value }
|
||||||
@@ -5119,7 +5125,7 @@ export function CompetitionManagement() {
|
|||||||
<div className="col-span-3">
|
<div className="col-span-3">
|
||||||
<Label className="text-xs">獎項名稱</Label>
|
<Label className="text-xs">獎項名稱</Label>
|
||||||
<Input
|
<Input
|
||||||
value={awardType.name || ""}
|
value={awardType.name ?? ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const updatedAwardTypes = [...newCompetition.teamConfig.awardTypes]
|
const updatedAwardTypes = [...newCompetition.teamConfig.awardTypes]
|
||||||
updatedAwardTypes[index] = { ...awardType, name: e.target.value }
|
updatedAwardTypes[index] = { ...awardType, name: e.target.value }
|
||||||
@@ -5138,7 +5144,7 @@ export function CompetitionManagement() {
|
|||||||
<div className="col-span-5">
|
<div className="col-span-5">
|
||||||
<Label className="text-xs">獎項描述</Label>
|
<Label className="text-xs">獎項描述</Label>
|
||||||
<Input
|
<Input
|
||||||
value={awardType.description || ""}
|
value={awardType.description ?? ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const updatedAwardTypes = [...newCompetition.teamConfig.awardTypes]
|
const updatedAwardTypes = [...newCompetition.teamConfig.awardTypes]
|
||||||
updatedAwardTypes[index] = { ...awardType, description: e.target.value }
|
updatedAwardTypes[index] = { ...awardType, description: e.target.value }
|
||||||
@@ -5157,7 +5163,7 @@ export function CompetitionManagement() {
|
|||||||
<div className="col-span-2">
|
<div className="col-span-2">
|
||||||
<Label className="text-xs">顏色主題</Label>
|
<Label className="text-xs">顏色主題</Label>
|
||||||
<Select
|
<Select
|
||||||
value={awardType.color || "text-yellow-600"}
|
value={awardType.color ?? "text-yellow-600"}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
const updatedAwardTypes = [...newCompetition.teamConfig.awardTypes]
|
const updatedAwardTypes = [...newCompetition.teamConfig.awardTypes]
|
||||||
updatedAwardTypes[index] = { ...awardType, color: value }
|
updatedAwardTypes[index] = { ...awardType, color: value }
|
||||||
@@ -5350,7 +5356,7 @@ export function CompetitionManagement() {
|
|||||||
<div className="col-span-4">
|
<div className="col-span-4">
|
||||||
<Label className="text-xs">評比項目名稱</Label>
|
<Label className="text-xs">評比項目名稱</Label>
|
||||||
<Input
|
<Input
|
||||||
value={rule.name || ""}
|
value={rule.name ?? ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const updatedRules = [...newCompetition.rules]
|
const updatedRules = [...newCompetition.rules]
|
||||||
updatedRules[index] = { ...rule, name: e.target.value }
|
updatedRules[index] = { ...rule, name: e.target.value }
|
||||||
@@ -5363,7 +5369,7 @@ export function CompetitionManagement() {
|
|||||||
<div className="col-span-5">
|
<div className="col-span-5">
|
||||||
<Label className="text-xs">描述</Label>
|
<Label className="text-xs">描述</Label>
|
||||||
<Input
|
<Input
|
||||||
value={rule.description || ""}
|
value={rule.description ?? ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const updatedRules = [...newCompetition.rules]
|
const updatedRules = [...newCompetition.rules]
|
||||||
updatedRules[index] = { ...rule, description: e.target.value }
|
updatedRules[index] = { ...rule, description: e.target.value }
|
||||||
@@ -5379,7 +5385,7 @@ export function CompetitionManagement() {
|
|||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
max="100"
|
max="100"
|
||||||
value={rule.weight || 0}
|
value={rule.weight ?? 0}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const updatedRules = [...newCompetition.rules]
|
const updatedRules = [...newCompetition.rules]
|
||||||
updatedRules[index] = { ...rule, weight: Number.parseInt(e.target.value) || 0 }
|
updatedRules[index] = { ...rule, weight: Number.parseInt(e.target.value) || 0 }
|
||||||
@@ -5468,7 +5474,7 @@ export function CompetitionManagement() {
|
|||||||
<div className="col-span-1">
|
<div className="col-span-1">
|
||||||
<Label className="text-xs">圖示</Label>
|
<Label className="text-xs">圖示</Label>
|
||||||
<Select
|
<Select
|
||||||
value={awardType.icon || "🏆"}
|
value={awardType.icon ?? "🏆"}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
const updatedAwardTypes = [...newCompetition.awardTypes]
|
const updatedAwardTypes = [...newCompetition.awardTypes]
|
||||||
updatedAwardTypes[index] = { ...awardType, icon: value }
|
updatedAwardTypes[index] = { ...awardType, icon: value }
|
||||||
@@ -5497,7 +5503,7 @@ export function CompetitionManagement() {
|
|||||||
<div className="col-span-3">
|
<div className="col-span-3">
|
||||||
<Label className="text-xs">獎項名稱</Label>
|
<Label className="text-xs">獎項名稱</Label>
|
||||||
<Input
|
<Input
|
||||||
value={awardType.name || ""}
|
value={awardType.name ?? ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const updatedAwardTypes = [...newCompetition.awardTypes]
|
const updatedAwardTypes = [...newCompetition.awardTypes]
|
||||||
updatedAwardTypes[index] = { ...awardType, name: e.target.value }
|
updatedAwardTypes[index] = { ...awardType, name: e.target.value }
|
||||||
@@ -5510,7 +5516,7 @@ export function CompetitionManagement() {
|
|||||||
<div className="col-span-5">
|
<div className="col-span-5">
|
||||||
<Label className="text-xs">獎項描述</Label>
|
<Label className="text-xs">獎項描述</Label>
|
||||||
<Input
|
<Input
|
||||||
value={awardType.description || ""}
|
value={awardType.description ?? ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const updatedAwardTypes = [...newCompetition.awardTypes]
|
const updatedAwardTypes = [...newCompetition.awardTypes]
|
||||||
updatedAwardTypes[index] = { ...awardType, description: e.target.value }
|
updatedAwardTypes[index] = { ...awardType, description: e.target.value }
|
||||||
@@ -5523,7 +5529,7 @@ export function CompetitionManagement() {
|
|||||||
<div className="col-span-2">
|
<div className="col-span-2">
|
||||||
<Label className="text-xs">顏色主題</Label>
|
<Label className="text-xs">顏色主題</Label>
|
||||||
<Select
|
<Select
|
||||||
value={awardType.color || "text-yellow-600"}
|
value={awardType.color ?? "text-yellow-600"}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
const updatedAwardTypes = [...newCompetition.awardTypes]
|
const updatedAwardTypes = [...newCompetition.awardTypes]
|
||||||
updatedAwardTypes[index] = { ...awardType, color: value }
|
updatedAwardTypes[index] = { ...awardType, color: value }
|
||||||
@@ -6175,8 +6181,12 @@ export function CompetitionManagement() {
|
|||||||
</h4>
|
</h4>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
{(selectedCompetitionForAction.teams && selectedCompetitionForAction.teams.length > 0) ?
|
{(selectedCompetitionForAction.teams && selectedCompetitionForAction.teams.length > 0) ?
|
||||||
selectedCompetitionForAction.teams.map((team: any) => (
|
selectedCompetitionForAction.teams
|
||||||
<div key={team.id} className="p-4 border rounded-lg bg-white space-y-2">
|
.filter((team: any, index: number, self: any[]) =>
|
||||||
|
self.findIndex(t => t.id === team.id) === index
|
||||||
|
)
|
||||||
|
.map((team: any, index: number) => (
|
||||||
|
<div key={`${team.id}-${index}`} className="p-4 border rounded-lg bg-white space-y-2">
|
||||||
<h5 className="font-medium text-gray-900">{team.name}</h5>
|
<h5 className="font-medium text-gray-900">{team.name}</h5>
|
||||||
<p className="text-sm text-gray-600">隊長:{team.leader_name || team.leader_id || '未知'}</p>
|
<p className="text-sm text-gray-600">隊長:{team.leader_name || team.leader_id || '未知'}</p>
|
||||||
<p className="text-sm text-gray-600">部門:{team.department || '未知'}</p>
|
<p className="text-sm text-gray-600">部門:{team.department || '未知'}</p>
|
||||||
@@ -6485,12 +6495,16 @@ export function CompetitionManagement() {
|
|||||||
(() => {
|
(() => {
|
||||||
// 使用selectedCompetition.teams數據,顯示為「團隊名 - APP名」格式
|
// 使用selectedCompetition.teams數據,顯示為「團隊名 - APP名」格式
|
||||||
const teamsData = selectedCompetition?.teams || []
|
const teamsData = selectedCompetition?.teams || []
|
||||||
return teamsData.length > 0 ? teamsData.map((team: any) => {
|
return teamsData.length > 0 ? teamsData
|
||||||
|
.filter((team: any, index: number, self: any[]) =>
|
||||||
|
self.findIndex(t => t.id === team.id) === index
|
||||||
|
)
|
||||||
|
.map((team: any, index: number) => {
|
||||||
// 檢查團隊是否有APP
|
// 檢查團隊是否有APP
|
||||||
const teamApps = team.apps || []
|
const teamApps = team.apps || []
|
||||||
if (teamApps.length === 0) {
|
if (teamApps.length === 0) {
|
||||||
return (
|
return (
|
||||||
<SelectItem key={team.id} value={team.id} disabled>
|
<SelectItem key={`${team.id}-${index}`} value={team.id} disabled>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Users className="w-4 h-4 text-gray-400" />
|
<Users className="w-4 h-4 text-gray-400" />
|
||||||
<span className="text-gray-400">{team.name}</span>
|
<span className="text-gray-400">{team.name}</span>
|
||||||
@@ -6503,7 +6517,7 @@ export function CompetitionManagement() {
|
|||||||
// 顯示團隊和其第一個APP
|
// 顯示團隊和其第一個APP
|
||||||
const firstApp = teamApps[0]
|
const firstApp = teamApps[0]
|
||||||
return (
|
return (
|
||||||
<SelectItem key={team.id} value={team.id}>
|
<SelectItem key={`${team.id}-${index}`} value={team.id}>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Users className="w-4 h-4 text-green-600" />
|
<Users className="w-4 h-4 text-green-600" />
|
||||||
<span>{team.name}</span>
|
<span>{team.name}</span>
|
||||||
@@ -7813,7 +7827,7 @@ export function CompetitionManagement() {
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>獎項類型</Label>
|
<Label>獎項類型</Label>
|
||||||
<Select
|
<Select
|
||||||
value={newAward.customAwardTypeId || newAward.awardType}
|
value={newAward.customAwardTypeId ?? newAward.awardType}
|
||||||
onValueChange={(value: any) => {
|
onValueChange={(value: any) => {
|
||||||
// 檢查是否為自定義獎項類型
|
// 檢查是否為自定義獎項類型
|
||||||
const customAwardType = competitionAwardTypes.find(type => type.id === value)
|
const customAwardType = competitionAwardTypes.find(type => type.id === value)
|
||||||
@@ -8095,8 +8109,12 @@ export function CompetitionManagement() {
|
|||||||
} else if (shouldShowTeam) {
|
} else if (shouldShowTeam) {
|
||||||
// 團體賽:只顯示團隊,不顯示 app
|
// 團體賽:只顯示團隊,不顯示 app
|
||||||
return competitionTeams.length > 0 ? (
|
return competitionTeams.length > 0 ? (
|
||||||
competitionTeams.map((team) => (
|
competitionTeams
|
||||||
<SelectItem key={team.id} value={team.id}>
|
.filter((team: any, index: number, self: any[]) =>
|
||||||
|
self.findIndex(t => t.id === team.id) === index
|
||||||
|
)
|
||||||
|
.map((team: any, index: number) => (
|
||||||
|
<SelectItem key={`${team.id}-${index}`} value={team.id}>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="font-medium">{team.name}</span>
|
<span className="font-medium">{team.name}</span>
|
||||||
<span className="text-xs text-gray-500">
|
<span className="text-xs text-gray-500">
|
||||||
@@ -8226,7 +8244,7 @@ export function CompetitionManagement() {
|
|||||||
})}
|
})}
|
||||||
placeholder="https://app.example.com"
|
placeholder="https://app.example.com"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-gray-500">生產環境應用連結</p>
|
<p className="text-xs text-gray-500">APP 應用連結</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 網站預覽內容 */}
|
{/* 網站預覽內容 */}
|
||||||
@@ -8574,7 +8592,7 @@ export function CompetitionManagement() {
|
|||||||
{/* 照片說明 */}
|
{/* 照片說明 */}
|
||||||
<Input
|
<Input
|
||||||
placeholder="輸入照片說明..."
|
placeholder="輸入照片說明..."
|
||||||
value={photo.caption || ''}
|
value={photo.caption ?? ''}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const updatedPhotos = newAward.photos.map(p =>
|
const updatedPhotos = newAward.photos.map(p =>
|
||||||
p.id === photo.id ? { ...p, caption: e.target.value } : p
|
p.id === photo.id ? { ...p, caption: e.target.value } : p
|
||||||
@@ -8925,7 +8943,7 @@ export function CompetitionManagement() {
|
|||||||
<div key="production" className="p-3 bg-green-50 border border-green-200 rounded-lg">
|
<div key="production" className="p-3 bg-green-50 border border-green-200 rounded-lg">
|
||||||
<div className="flex items-center space-x-2 mb-2">
|
<div className="flex items-center space-x-2 mb-2">
|
||||||
<div className="w-3 h-3 bg-green-500 rounded-full"></div>
|
<div className="w-3 h-3 bg-green-500 rounded-full"></div>
|
||||||
<span className="text-sm font-medium text-green-900">生產環境</span>
|
<span className="text-sm font-medium text-green-900">APP 連結</span>
|
||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
href={links.production}
|
href={links.production}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useState } from "react"
|
import { useState, useEffect } from "react"
|
||||||
import { useCompetition } from "@/contexts/competition-context"
|
import { useCompetition } from "@/contexts/competition-context"
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
@@ -58,11 +58,38 @@ export function AwardDetailDialog({ open, onOpenChange, award }: AwardDetailDial
|
|||||||
const [activeTab, setActiveTab] = useState("overview")
|
const [activeTab, setActiveTab] = useState("overview")
|
||||||
const [showPhotoGallery, setShowPhotoGallery] = useState(false)
|
const [showPhotoGallery, setShowPhotoGallery] = useState(false)
|
||||||
const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0)
|
const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0)
|
||||||
|
const [competitionJudges, setCompetitionJudges] = useState<any[]>([])
|
||||||
|
|
||||||
const competition = competitions.find((c) => c.id === award.competitionId)
|
const competition = competitions.find((c) => c.id === award.competitionId)
|
||||||
const judgeScores = getJudgeScores(award.id)
|
const judgeScores = getJudgeScores(award.id)
|
||||||
const appData = getAppData(award.id)
|
const appData = getAppData(award.id)
|
||||||
|
|
||||||
|
// 載入競賽評審團資訊
|
||||||
|
useEffect(() => {
|
||||||
|
if (open && award.competitionId) {
|
||||||
|
const loadCompetitionJudges = async () => {
|
||||||
|
try {
|
||||||
|
console.log('🔍 載入競賽評審團:', award.competitionId);
|
||||||
|
const response = await fetch(`/api/competitions/${award.competitionId}/judges`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success && data.data && data.data.judges) {
|
||||||
|
console.log('✅ 獲取到評審團:', data.data.judges.length, '位');
|
||||||
|
setCompetitionJudges(data.data.judges);
|
||||||
|
} else {
|
||||||
|
console.error('❌ 獲取評審團失敗:', data.message);
|
||||||
|
setCompetitionJudges([]);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 載入評審團失敗:', error);
|
||||||
|
setCompetitionJudges([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadCompetitionJudges();
|
||||||
|
}
|
||||||
|
}, [open, award.competitionId]);
|
||||||
|
|
||||||
// Competition photos - empty for production
|
// Competition photos - empty for production
|
||||||
const getCompetitionPhotos = () => {
|
const getCompetitionPhotos = () => {
|
||||||
return []
|
return []
|
||||||
@@ -224,10 +251,20 @@ export function AwardDetailDialog({ open, onOpenChange, award }: AwardDetailDial
|
|||||||
<strong>競賽描述:</strong>
|
<strong>競賽描述:</strong>
|
||||||
{competition.description}
|
{competition.description}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p className="mb-2">
|
||||||
<strong>競賽期間:</strong>
|
<strong>競賽期間:</strong>
|
||||||
{competition.startDate} ~ {competition.endDate}
|
{competition.startDate} ~ {competition.endDate}
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>評審團:</strong>
|
||||||
|
{competitionJudges && competitionJudges.length > 0 ? (
|
||||||
|
<span className="text-green-700">
|
||||||
|
{competitionJudges.length} 位評審
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="text-gray-500">暫無評審信息</span>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -251,7 +288,7 @@ export function AwardDetailDialog({ open, onOpenChange, award }: AwardDetailDial
|
|||||||
<ExternalLink className="w-5 h-5 text-green-600" />
|
<ExternalLink className="w-5 h-5 text-green-600" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-green-800">正式應用</p>
|
<p className="font-medium text-green-800">正式應用</p>
|
||||||
<p className="text-xs text-green-600">生產環境</p>
|
<p className="text-xs text-green-600">APP 連結</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@@ -407,7 +444,25 @@ export function AwardDetailDialog({ open, onOpenChange, award }: AwardDetailDial
|
|||||||
)
|
)
|
||||||
|
|
||||||
const renderJudgePanel = () => {
|
const renderJudgePanel = () => {
|
||||||
if (!competition) return null
|
if (!competitionJudges || competitionJudges.length === 0) {
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center space-x-2">
|
||||||
|
<Crown className="w-5 h-5 text-purple-500" />
|
||||||
|
<span>評審團</span>
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>本次競賽的專業評審團隊</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="text-center py-8 text-gray-500">
|
||||||
|
<Crown className="w-12 h-12 mx-auto mb-4 text-gray-300" />
|
||||||
|
<p>暫無評審信息</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
@@ -420,11 +475,7 @@ export function AwardDetailDialog({ open, onOpenChange, award }: AwardDetailDial
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
{competition.judges.map((judgeId) => {
|
{competitionJudges.map((judge) => (
|
||||||
const judge = judges.find((j) => j.id === judgeId)
|
|
||||||
if (!judge) return null
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={judge.id} className="flex items-center space-x-3 p-3 bg-gray-50 rounded-lg">
|
<div key={judge.id} className="flex items-center space-x-3 p-3 bg-gray-50 rounded-lg">
|
||||||
<Avatar>
|
<Avatar>
|
||||||
<AvatarImage src={judge.avatar} />
|
<AvatarImage src={judge.avatar} />
|
||||||
@@ -434,7 +485,7 @@ export function AwardDetailDialog({ open, onOpenChange, award }: AwardDetailDial
|
|||||||
<h4 className="font-medium">{judge.name}</h4>
|
<h4 className="font-medium">{judge.name}</h4>
|
||||||
<p className="text-sm text-gray-600">{judge.title}</p>
|
<p className="text-sm text-gray-600">{judge.title}</p>
|
||||||
<div className="flex flex-wrap gap-1 mt-1">
|
<div className="flex flex-wrap gap-1 mt-1">
|
||||||
{judge.expertise.slice(0, 2).map((skill) => (
|
{judge.expertise && judge.expertise.slice(0, 2).map((skill) => (
|
||||||
<Badge key={skill} variant="secondary" className="text-xs">
|
<Badge key={skill} variant="secondary" className="text-xs">
|
||||||
{skill}
|
{skill}
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -442,8 +493,7 @@ export function AwardDetailDialog({ open, onOpenChange, award }: AwardDetailDial
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
))}
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
27
database-awards-extension.sql
Normal file
27
database-awards-extension.sql
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
-- =====================================================
|
||||||
|
-- 擴展 awards 表結構
|
||||||
|
-- 添加缺少的欄位以支持完整的獎項功能
|
||||||
|
-- =====================================================
|
||||||
|
|
||||||
|
-- 添加獎項描述欄位
|
||||||
|
ALTER TABLE `awards`
|
||||||
|
ADD COLUMN `description` TEXT NULL COMMENT '獎項描述' AFTER `category`;
|
||||||
|
|
||||||
|
-- 添加評審評語欄位
|
||||||
|
ALTER TABLE `awards`
|
||||||
|
ADD COLUMN `judge_comments` TEXT NULL COMMENT '評審評語' AFTER `description`;
|
||||||
|
|
||||||
|
-- 添加應用連結欄位(JSON 格式)
|
||||||
|
ALTER TABLE `awards`
|
||||||
|
ADD COLUMN `application_links` JSON NULL COMMENT '應用連結' AFTER `judge_comments`;
|
||||||
|
|
||||||
|
-- 添加相關文檔欄位(JSON 格式)
|
||||||
|
ALTER TABLE `awards`
|
||||||
|
ADD COLUMN `documents` JSON NULL COMMENT '相關文檔' AFTER `application_links`;
|
||||||
|
|
||||||
|
-- 添加得獎照片欄位(JSON 格式)
|
||||||
|
ALTER TABLE `awards`
|
||||||
|
ADD COLUMN `photos` JSON NULL COMMENT '得獎照片' AFTER `documents`;
|
||||||
|
|
||||||
|
-- 驗證欄位是否添加成功
|
||||||
|
DESCRIBE `awards`;
|
Reference in New Issue
Block a user