修正獎勵專區評審團資料異常 bug
This commit is contained in:
@@ -2143,11 +2143,17 @@ export function CompetitionManagement() {
|
||||
|
||||
if (teamsData.success && teamsData.data && teamsData.data.teams) {
|
||||
console.log('✅ 競賽團隊載入成功:', teamsData.data.teams.length, '個團隊')
|
||||
updatedCompetition.teams = teamsData.data.teams
|
||||
// 同時更新dbTeams
|
||||
// 確保團隊數據的唯一性,避免重複的ID
|
||||
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 => {
|
||||
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]
|
||||
})
|
||||
} else {
|
||||
@@ -3195,8 +3201,8 @@ export function CompetitionManagement() {
|
||||
)
|
||||
}
|
||||
|
||||
return paginatedTeams.map((team) => (
|
||||
<Card key={team.id} className="relative h-fit">
|
||||
return paginatedTeams.map((team, index) => (
|
||||
<Card key={`${team.id}-${index}`} className="relative h-fit">
|
||||
<CardContent className="p-4">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-start justify-between">
|
||||
@@ -4067,7 +4073,7 @@ export function CompetitionManagement() {
|
||||
if (links && links.production) {
|
||||
linkItems.push(
|
||||
<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
|
||||
href={links.production}
|
||||
target="_blank"
|
||||
@@ -4075,7 +4081,7 @@ export function CompetitionManagement() {
|
||||
className="text-xs text-blue-600 hover:text-blue-800 underline truncate max-w-24"
|
||||
title={links.production}
|
||||
>
|
||||
生產環境
|
||||
APP 連結
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
@@ -4538,7 +4544,7 @@ export function CompetitionManagement() {
|
||||
<div className="col-span-4">
|
||||
<Label className="text-xs">評比項目名稱</Label>
|
||||
<Input
|
||||
value={rule.name || ""}
|
||||
value={rule.name ?? ""}
|
||||
onChange={(e) => {
|
||||
const updatedRules = [...newCompetition.individualConfig.rules]
|
||||
updatedRules[index] = { ...rule, name: e.target.value }
|
||||
@@ -4557,7 +4563,7 @@ export function CompetitionManagement() {
|
||||
<div className="col-span-5">
|
||||
<Label className="text-xs">描述</Label>
|
||||
<Input
|
||||
value={rule.description || ""}
|
||||
value={rule.description ?? ""}
|
||||
onChange={(e) => {
|
||||
const updatedRules = [...newCompetition.individualConfig.rules]
|
||||
updatedRules[index] = { ...rule, description: e.target.value }
|
||||
@@ -4579,7 +4585,7 @@ export function CompetitionManagement() {
|
||||
type="number"
|
||||
min="0"
|
||||
max="100"
|
||||
value={rule.weight || 0}
|
||||
value={rule.weight ?? 0}
|
||||
onChange={(e) => {
|
||||
const updatedRules = [...newCompetition.individualConfig.rules]
|
||||
updatedRules[index] = { ...rule, weight: Number.parseInt(e.target.value) || 0 }
|
||||
@@ -4672,7 +4678,7 @@ export function CompetitionManagement() {
|
||||
<div className="col-span-1">
|
||||
<Label className="text-xs">圖示</Label>
|
||||
<Select
|
||||
value={awardType.icon || "🏆"}
|
||||
value={awardType.icon ?? "🏆"}
|
||||
onValueChange={(value) => {
|
||||
const updatedAwardTypes = [...newCompetition.individualConfig.awardTypes]
|
||||
updatedAwardTypes[index] = { ...awardType, icon: value }
|
||||
@@ -4706,7 +4712,7 @@ export function CompetitionManagement() {
|
||||
<div className="col-span-3">
|
||||
<Label className="text-xs">獎項名稱</Label>
|
||||
<Input
|
||||
value={awardType.name || ""}
|
||||
value={awardType.name ?? ""}
|
||||
onChange={(e) => {
|
||||
const updatedAwardTypes = [...newCompetition.individualConfig.awardTypes]
|
||||
updatedAwardTypes[index] = { ...awardType, name: e.target.value }
|
||||
@@ -4725,7 +4731,7 @@ export function CompetitionManagement() {
|
||||
<div className="col-span-5">
|
||||
<Label className="text-xs">獎項描述</Label>
|
||||
<Input
|
||||
value={awardType.description || ""}
|
||||
value={awardType.description ?? ""}
|
||||
onChange={(e) => {
|
||||
const updatedAwardTypes = [...newCompetition.individualConfig.awardTypes]
|
||||
updatedAwardTypes[index] = { ...awardType, description: e.target.value }
|
||||
@@ -4744,7 +4750,7 @@ export function CompetitionManagement() {
|
||||
<div className="col-span-2">
|
||||
<Label className="text-xs">顏色主題</Label>
|
||||
<Select
|
||||
value={awardType.color || "text-yellow-600"}
|
||||
value={awardType.color ?? "text-yellow-600"}
|
||||
onValueChange={(value) => {
|
||||
const updatedAwardTypes = [...newCompetition.individualConfig.awardTypes]
|
||||
updatedAwardTypes[index] = { ...awardType, color: value }
|
||||
@@ -4950,7 +4956,7 @@ export function CompetitionManagement() {
|
||||
<div className="col-span-4">
|
||||
<Label className="text-xs">評比項目名稱</Label>
|
||||
<Input
|
||||
value={rule.name || ""}
|
||||
value={rule.name ?? ""}
|
||||
onChange={(e) => {
|
||||
const updatedRules = [...newCompetition.teamConfig.rules]
|
||||
updatedRules[index] = { ...rule, name: e.target.value }
|
||||
@@ -4969,7 +4975,7 @@ export function CompetitionManagement() {
|
||||
<div className="col-span-5">
|
||||
<Label className="text-xs">描述</Label>
|
||||
<Input
|
||||
value={rule.description || ""}
|
||||
value={rule.description ?? ""}
|
||||
onChange={(e) => {
|
||||
const updatedRules = [...newCompetition.teamConfig.rules]
|
||||
updatedRules[index] = { ...rule, description: e.target.value }
|
||||
@@ -4991,7 +4997,7 @@ export function CompetitionManagement() {
|
||||
type="number"
|
||||
min="0"
|
||||
max="100"
|
||||
value={rule.weight || 0}
|
||||
value={rule.weight ?? 0}
|
||||
onChange={(e) => {
|
||||
const updatedRules = [...newCompetition.teamConfig.rules]
|
||||
updatedRules[index] = { ...rule, weight: Number.parseInt(e.target.value) || 0 }
|
||||
@@ -5084,7 +5090,7 @@ export function CompetitionManagement() {
|
||||
<div className="col-span-1">
|
||||
<Label className="text-xs">圖示</Label>
|
||||
<Select
|
||||
value={awardType.icon || "🏆"}
|
||||
value={awardType.icon ?? "🏆"}
|
||||
onValueChange={(value) => {
|
||||
const updatedAwardTypes = [...newCompetition.teamConfig.awardTypes]
|
||||
updatedAwardTypes[index] = { ...awardType, icon: value }
|
||||
@@ -5119,7 +5125,7 @@ export function CompetitionManagement() {
|
||||
<div className="col-span-3">
|
||||
<Label className="text-xs">獎項名稱</Label>
|
||||
<Input
|
||||
value={awardType.name || ""}
|
||||
value={awardType.name ?? ""}
|
||||
onChange={(e) => {
|
||||
const updatedAwardTypes = [...newCompetition.teamConfig.awardTypes]
|
||||
updatedAwardTypes[index] = { ...awardType, name: e.target.value }
|
||||
@@ -5138,7 +5144,7 @@ export function CompetitionManagement() {
|
||||
<div className="col-span-5">
|
||||
<Label className="text-xs">獎項描述</Label>
|
||||
<Input
|
||||
value={awardType.description || ""}
|
||||
value={awardType.description ?? ""}
|
||||
onChange={(e) => {
|
||||
const updatedAwardTypes = [...newCompetition.teamConfig.awardTypes]
|
||||
updatedAwardTypes[index] = { ...awardType, description: e.target.value }
|
||||
@@ -5157,7 +5163,7 @@ export function CompetitionManagement() {
|
||||
<div className="col-span-2">
|
||||
<Label className="text-xs">顏色主題</Label>
|
||||
<Select
|
||||
value={awardType.color || "text-yellow-600"}
|
||||
value={awardType.color ?? "text-yellow-600"}
|
||||
onValueChange={(value) => {
|
||||
const updatedAwardTypes = [...newCompetition.teamConfig.awardTypes]
|
||||
updatedAwardTypes[index] = { ...awardType, color: value }
|
||||
@@ -5350,7 +5356,7 @@ export function CompetitionManagement() {
|
||||
<div className="col-span-4">
|
||||
<Label className="text-xs">評比項目名稱</Label>
|
||||
<Input
|
||||
value={rule.name || ""}
|
||||
value={rule.name ?? ""}
|
||||
onChange={(e) => {
|
||||
const updatedRules = [...newCompetition.rules]
|
||||
updatedRules[index] = { ...rule, name: e.target.value }
|
||||
@@ -5363,7 +5369,7 @@ export function CompetitionManagement() {
|
||||
<div className="col-span-5">
|
||||
<Label className="text-xs">描述</Label>
|
||||
<Input
|
||||
value={rule.description || ""}
|
||||
value={rule.description ?? ""}
|
||||
onChange={(e) => {
|
||||
const updatedRules = [...newCompetition.rules]
|
||||
updatedRules[index] = { ...rule, description: e.target.value }
|
||||
@@ -5379,7 +5385,7 @@ export function CompetitionManagement() {
|
||||
type="number"
|
||||
min="0"
|
||||
max="100"
|
||||
value={rule.weight || 0}
|
||||
value={rule.weight ?? 0}
|
||||
onChange={(e) => {
|
||||
const updatedRules = [...newCompetition.rules]
|
||||
updatedRules[index] = { ...rule, weight: Number.parseInt(e.target.value) || 0 }
|
||||
@@ -5468,7 +5474,7 @@ export function CompetitionManagement() {
|
||||
<div className="col-span-1">
|
||||
<Label className="text-xs">圖示</Label>
|
||||
<Select
|
||||
value={awardType.icon || "🏆"}
|
||||
value={awardType.icon ?? "🏆"}
|
||||
onValueChange={(value) => {
|
||||
const updatedAwardTypes = [...newCompetition.awardTypes]
|
||||
updatedAwardTypes[index] = { ...awardType, icon: value }
|
||||
@@ -5497,7 +5503,7 @@ export function CompetitionManagement() {
|
||||
<div className="col-span-3">
|
||||
<Label className="text-xs">獎項名稱</Label>
|
||||
<Input
|
||||
value={awardType.name || ""}
|
||||
value={awardType.name ?? ""}
|
||||
onChange={(e) => {
|
||||
const updatedAwardTypes = [...newCompetition.awardTypes]
|
||||
updatedAwardTypes[index] = { ...awardType, name: e.target.value }
|
||||
@@ -5510,7 +5516,7 @@ export function CompetitionManagement() {
|
||||
<div className="col-span-5">
|
||||
<Label className="text-xs">獎項描述</Label>
|
||||
<Input
|
||||
value={awardType.description || ""}
|
||||
value={awardType.description ?? ""}
|
||||
onChange={(e) => {
|
||||
const updatedAwardTypes = [...newCompetition.awardTypes]
|
||||
updatedAwardTypes[index] = { ...awardType, description: e.target.value }
|
||||
@@ -5523,7 +5529,7 @@ export function CompetitionManagement() {
|
||||
<div className="col-span-2">
|
||||
<Label className="text-xs">顏色主題</Label>
|
||||
<Select
|
||||
value={awardType.color || "text-yellow-600"}
|
||||
value={awardType.color ?? "text-yellow-600"}
|
||||
onValueChange={(value) => {
|
||||
const updatedAwardTypes = [...newCompetition.awardTypes]
|
||||
updatedAwardTypes[index] = { ...awardType, color: value }
|
||||
@@ -6175,8 +6181,12 @@ export function CompetitionManagement() {
|
||||
</h4>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{(selectedCompetitionForAction.teams && selectedCompetitionForAction.teams.length > 0) ?
|
||||
selectedCompetitionForAction.teams.map((team: any) => (
|
||||
<div key={team.id} className="p-4 border rounded-lg bg-white space-y-2">
|
||||
selectedCompetitionForAction.teams
|
||||
.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>
|
||||
<p className="text-sm text-gray-600">隊長:{team.leader_name || team.leader_id || '未知'}</p>
|
||||
<p className="text-sm text-gray-600">部門:{team.department || '未知'}</p>
|
||||
@@ -6485,12 +6495,16 @@ export function CompetitionManagement() {
|
||||
(() => {
|
||||
// 使用selectedCompetition.teams數據,顯示為「團隊名 - APP名」格式
|
||||
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
|
||||
const teamApps = team.apps || []
|
||||
if (teamApps.length === 0) {
|
||||
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">
|
||||
<Users className="w-4 h-4 text-gray-400" />
|
||||
<span className="text-gray-400">{team.name}</span>
|
||||
@@ -6503,7 +6517,7 @@ export function CompetitionManagement() {
|
||||
// 顯示團隊和其第一個APP
|
||||
const firstApp = teamApps[0]
|
||||
return (
|
||||
<SelectItem key={team.id} value={team.id}>
|
||||
<SelectItem key={`${team.id}-${index}`} value={team.id}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Users className="w-4 h-4 text-green-600" />
|
||||
<span>{team.name}</span>
|
||||
@@ -7813,7 +7827,7 @@ export function CompetitionManagement() {
|
||||
<div className="space-y-2">
|
||||
<Label>獎項類型</Label>
|
||||
<Select
|
||||
value={newAward.customAwardTypeId || newAward.awardType}
|
||||
value={newAward.customAwardTypeId ?? newAward.awardType}
|
||||
onValueChange={(value: any) => {
|
||||
// 檢查是否為自定義獎項類型
|
||||
const customAwardType = competitionAwardTypes.find(type => type.id === value)
|
||||
@@ -8095,8 +8109,12 @@ export function CompetitionManagement() {
|
||||
} else if (shouldShowTeam) {
|
||||
// 團體賽:只顯示團隊,不顯示 app
|
||||
return competitionTeams.length > 0 ? (
|
||||
competitionTeams.map((team) => (
|
||||
<SelectItem key={team.id} value={team.id}>
|
||||
competitionTeams
|
||||
.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">
|
||||
<span className="font-medium">{team.name}</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
@@ -8226,7 +8244,7 @@ export function CompetitionManagement() {
|
||||
})}
|
||||
placeholder="https://app.example.com"
|
||||
/>
|
||||
<p className="text-xs text-gray-500">生產環境應用連結</p>
|
||||
<p className="text-xs text-gray-500">APP 應用連結</p>
|
||||
</div>
|
||||
|
||||
{/* 網站預覽內容 */}
|
||||
@@ -8574,7 +8592,7 @@ export function CompetitionManagement() {
|
||||
{/* 照片說明 */}
|
||||
<Input
|
||||
placeholder="輸入照片說明..."
|
||||
value={photo.caption || ''}
|
||||
value={photo.caption ?? ''}
|
||||
onChange={(e) => {
|
||||
const updatedPhotos = newAward.photos.map(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 className="flex items-center space-x-2 mb-2">
|
||||
<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>
|
||||
<a
|
||||
href={links.production}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useState, useEffect } from "react"
|
||||
import { useCompetition } from "@/contexts/competition-context"
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||
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 [showPhotoGallery, setShowPhotoGallery] = useState(false)
|
||||
const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0)
|
||||
const [competitionJudges, setCompetitionJudges] = useState<any[]>([])
|
||||
|
||||
const competition = competitions.find((c) => c.id === award.competitionId)
|
||||
const judgeScores = getJudgeScores(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
|
||||
const getCompetitionPhotos = () => {
|
||||
return []
|
||||
@@ -224,10 +251,20 @@ export function AwardDetailDialog({ open, onOpenChange, award }: AwardDetailDial
|
||||
<strong>競賽描述:</strong>
|
||||
{competition.description}
|
||||
</p>
|
||||
<p>
|
||||
<p className="mb-2">
|
||||
<strong>競賽期間:</strong>
|
||||
{competition.startDate} ~ {competition.endDate}
|
||||
</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>
|
||||
)}
|
||||
@@ -251,7 +288,7 @@ export function AwardDetailDialog({ open, onOpenChange, award }: AwardDetailDial
|
||||
<ExternalLink className="w-5 h-5 text-green-600" />
|
||||
<div>
|
||||
<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>
|
||||
<Button
|
||||
@@ -407,7 +444,25 @@ export function AwardDetailDialog({ open, onOpenChange, award }: AwardDetailDial
|
||||
)
|
||||
|
||||
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 (
|
||||
<Card>
|
||||
@@ -420,30 +475,25 @@ export function AwardDetailDialog({ open, onOpenChange, award }: AwardDetailDial
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{competition.judges.map((judgeId) => {
|
||||
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">
|
||||
<Avatar>
|
||||
<AvatarImage src={judge.avatar} />
|
||||
<AvatarFallback className="bg-purple-100 text-purple-700">{judge.name[0]}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1">
|
||||
<h4 className="font-medium">{judge.name}</h4>
|
||||
<p className="text-sm text-gray-600">{judge.title}</p>
|
||||
<div className="flex flex-wrap gap-1 mt-1">
|
||||
{judge.expertise.slice(0, 2).map((skill) => (
|
||||
<Badge key={skill} variant="secondary" className="text-xs">
|
||||
{skill}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
{competitionJudges.map((judge) => (
|
||||
<div key={judge.id} className="flex items-center space-x-3 p-3 bg-gray-50 rounded-lg">
|
||||
<Avatar>
|
||||
<AvatarImage src={judge.avatar} />
|
||||
<AvatarFallback className="bg-purple-100 text-purple-700">{judge.name[0]}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1">
|
||||
<h4 className="font-medium">{judge.name}</h4>
|
||||
<p className="text-sm text-gray-600">{judge.title}</p>
|
||||
<div className="flex flex-wrap gap-1 mt-1">
|
||||
{judge.expertise && judge.expertise.slice(0, 2).map((skill) => (
|
||||
<Badge key={skill} variant="secondary" className="text-xs">
|
||||
{skill}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</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