修正評審評分清單失敗問題
This commit is contained in:
@@ -7,51 +7,10 @@ import { db } from '@/lib/database';
|
|||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
console.log('🚀 ========== 應用 API 開始執行 ==========');
|
|
||||||
const { searchParams } = new URL(request.url);
|
const { searchParams } = new URL(request.url);
|
||||||
const teamId = searchParams.get('teamId');
|
const teamId = searchParams.get('teamId');
|
||||||
|
|
||||||
console.log('🔍 獲取可用應用列表, teamId:', teamId);
|
|
||||||
console.log('🔍 請求 URL:', request.url);
|
|
||||||
|
|
||||||
// 先檢查所有應用
|
|
||||||
console.log('📊 開始檢查數據庫...');
|
|
||||||
const allAppsSql = `SELECT COUNT(*) as count FROM apps`;
|
|
||||||
const allAppsResult = await db.query(allAppsSql);
|
|
||||||
console.log('📊 數據庫中應用總數:', allAppsResult[0].count);
|
|
||||||
|
|
||||||
// 檢查活躍應用
|
|
||||||
const activeAppsSql = `SELECT COUNT(*) as count FROM apps WHERE is_active = TRUE`;
|
|
||||||
const activeAppsResult = await db.query(activeAppsSql);
|
|
||||||
console.log('✅ 活躍應用數量 (is_active = TRUE):', activeAppsResult[0].count);
|
|
||||||
|
|
||||||
// 檢查所有應用的 is_active 值
|
|
||||||
const allAppsWithStatusSql = `SELECT id, name, is_active, team_id FROM apps LIMIT 5`;
|
|
||||||
const allAppsWithStatusResult = await db.query(allAppsWithStatusSql);
|
|
||||||
console.log('📋 前5個應用的狀態:', allAppsWithStatusResult);
|
|
||||||
|
|
||||||
// 檢查是否有 is_active = 1 的應用
|
|
||||||
const activeAppsWith1Sql = `SELECT COUNT(*) as count FROM apps WHERE is_active = 1`;
|
|
||||||
const activeAppsWith1Result = await db.query(activeAppsWith1Sql);
|
|
||||||
console.log('✅ is_active = 1 的應用數量:', activeAppsWith1Result[0].count);
|
|
||||||
|
|
||||||
// 檢查是否有 is_active = '1' 的應用(字符串)
|
|
||||||
const activeAppsWithStringSql = `SELECT COUNT(*) as count FROM apps WHERE is_active = '1'`;
|
|
||||||
const activeAppsWithStringResult = await db.query(activeAppsWithStringSql);
|
|
||||||
console.log('✅ is_active = "1" 的應用數量:', activeAppsWithStringResult[0].count);
|
|
||||||
|
|
||||||
// 檢查沒有團隊的應用
|
|
||||||
const noTeamAppsSql = `SELECT COUNT(*) as count FROM apps WHERE is_active = 1 AND team_id IS NULL`;
|
|
||||||
const noTeamAppsResult = await db.query(noTeamAppsSql);
|
|
||||||
console.log('🔓 沒有團隊的應用數量:', noTeamAppsResult[0].count);
|
|
||||||
|
|
||||||
// 檢查屬於其他團隊的應用
|
|
||||||
const otherTeamAppsSql = `SELECT COUNT(*) as count FROM apps WHERE is_active = 1 AND team_id IS NOT NULL AND team_id != ?`;
|
|
||||||
const otherTeamAppsResult = await db.query(otherTeamAppsSql, [teamId || '']);
|
|
||||||
console.log('🔓 屬於其他團隊的應用數量:', otherTeamAppsResult[0].count);
|
|
||||||
|
|
||||||
// 獲取所有活躍的應用,編輯團隊時顯示所有應用(包括已綁定的)
|
// 獲取所有活躍的應用,編輯團隊時顯示所有應用(包括已綁定的)
|
||||||
// 使用 is_active = 1 因為數據庫中存儲的是數字 1
|
|
||||||
// 與 users 表 JOIN 獲取創建者姓名
|
// 與 users 表 JOIN 獲取創建者姓名
|
||||||
let sql = `
|
let sql = `
|
||||||
SELECT a.id, a.name, a.description, a.category, a.type, a.icon, a.icon_color, a.app_url,
|
SELECT a.id, a.name, a.description, a.category, a.type, a.icon, a.icon_color, a.app_url,
|
||||||
@@ -65,48 +24,36 @@ export async function GET(request: NextRequest) {
|
|||||||
|
|
||||||
const params: any[] = [];
|
const params: any[] = [];
|
||||||
|
|
||||||
console.log('📝 執行的 SQL:', sql);
|
|
||||||
console.log('📝 參數:', params);
|
|
||||||
|
|
||||||
const apps = await db.query(sql, params);
|
const apps = await db.query(sql, params);
|
||||||
console.log('📊 查詢結果:', apps.length, '個應用');
|
|
||||||
|
|
||||||
// 如果沒有結果,嘗試不同的查詢條件
|
// 如果沒有結果,嘗試不同的查詢條件
|
||||||
if (apps.length === 0) {
|
if (apps.length === 0) {
|
||||||
console.log('⚠️ 沒有找到 is_active = 1 的應用,嘗試其他查詢條件...');
|
|
||||||
|
|
||||||
// 嘗試 is_active = TRUE
|
// 嘗試 is_active = TRUE
|
||||||
const sqlTrue = sql.replace('WHERE a.is_active = 1', 'WHERE a.is_active = TRUE');
|
const sqlTrue = sql.replace('WHERE a.is_active = 1', 'WHERE a.is_active = TRUE');
|
||||||
const appsTrue = await db.query(sqlTrue, params);
|
const appsTrue = await db.query(sqlTrue, params);
|
||||||
console.log('📊 is_active = TRUE 查詢結果:', appsTrue.length, '個應用');
|
|
||||||
|
|
||||||
// 嘗試 is_active = '1'
|
// 嘗試 is_active = '1'
|
||||||
const sqlString = sql.replace('WHERE a.is_active = 1', 'WHERE a.is_active = "1"');
|
const sqlString = sql.replace('WHERE a.is_active = 1', 'WHERE a.is_active = "1"');
|
||||||
const appsString = await db.query(sqlString, params);
|
const appsString = await db.query(sqlString, params);
|
||||||
console.log('📊 is_active = "1" 查詢結果:', appsString.length, '個應用');
|
|
||||||
|
|
||||||
// 嘗試沒有 is_active 條件
|
// 嘗試沒有 is_active 條件
|
||||||
const sqlNoFilter = sql.replace('WHERE a.is_active = 1', 'WHERE 1=1');
|
const sqlNoFilter = sql.replace('WHERE a.is_active = 1', 'WHERE 1=1');
|
||||||
const appsNoFilter = await db.query(sqlNoFilter, params);
|
const appsNoFilter = await db.query(sqlNoFilter, params);
|
||||||
console.log('📊 無 is_active 過濾查詢結果:', appsNoFilter.length, '個應用');
|
|
||||||
|
|
||||||
// 使用有結果的查詢
|
// 使用有結果的查詢
|
||||||
if (appsTrue.length > 0) {
|
if (appsTrue.length > 0) {
|
||||||
console.log('✅ 使用 is_active = TRUE 的結果');
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: '可用應用列表獲取成功',
|
message: '可用應用列表獲取成功',
|
||||||
data: appsTrue
|
data: appsTrue
|
||||||
});
|
});
|
||||||
} else if (appsString.length > 0) {
|
} else if (appsString.length > 0) {
|
||||||
console.log('✅ 使用 is_active = "1" 的結果');
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: '可用應用列表獲取成功',
|
message: '可用應用列表獲取成功',
|
||||||
data: appsString
|
data: appsString
|
||||||
});
|
});
|
||||||
} else if (appsNoFilter.length > 0) {
|
} else if (appsNoFilter.length > 0) {
|
||||||
console.log('✅ 使用無過濾條件的結果');
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: '可用應用列表獲取成功',
|
message: '可用應用列表獲取成功',
|
||||||
@@ -115,7 +62,6 @@ export async function GET(request: NextRequest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('🚀 ========== 應用 API 執行完成 ==========');
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: '可用應用列表獲取成功',
|
message: '可用應用列表獲取成功',
|
||||||
|
@@ -7,19 +7,14 @@ import { db } from '@/lib/database';
|
|||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
console.log('🧪 開始測試資料庫連接...');
|
|
||||||
|
|
||||||
// 測試基本查詢
|
// 測試基本查詢
|
||||||
const result = await db.query('SELECT 1 as test');
|
const result = await db.query('SELECT 1 as test');
|
||||||
console.log('✅ 基本查詢成功:', result);
|
|
||||||
|
|
||||||
// 測試競賽表
|
// 測試競賽表
|
||||||
const competitions = await db.query('SELECT id, name, type FROM competitions WHERE is_active = TRUE LIMIT 3');
|
const competitions = await db.query('SELECT id, name, type FROM competitions WHERE is_active = TRUE LIMIT 3');
|
||||||
console.log('✅ 競賽查詢成功:', competitions);
|
|
||||||
|
|
||||||
// 測試評審表
|
// 測試評審表
|
||||||
const judges = await db.query('SELECT id, name, title FROM judges WHERE is_active = TRUE LIMIT 3');
|
const judges = await db.query('SELECT id, name, title FROM judges WHERE is_active = TRUE LIMIT 3');
|
||||||
console.log('✅ 評審查詢成功:', judges);
|
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
|
@@ -14,7 +14,6 @@ export default function DebugScoringPage() {
|
|||||||
|
|
||||||
const addLog = (message: string) => {
|
const addLog = (message: string) => {
|
||||||
setLogs(prev => [...prev, `${new Date().toLocaleTimeString()}: ${message}`])
|
setLogs(prev => [...prev, `${new Date().toLocaleTimeString()}: ${message}`])
|
||||||
console.log(message)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 載入競賽列表
|
// 載入競賽列表
|
||||||
|
@@ -63,7 +63,7 @@ export default function JudgeScoringPage() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// 獲取評審的評分任務
|
// 獲取評審的評分任務
|
||||||
const response = await fetch(`/api/judge/scoring-tasks?judgeId=${judgeId}`)
|
const response = await fetch(`/api/judge/scoring-tasks?judgeId=${judgeId}&competitionId=0fffae9a-9539-11f0-b5d9-6e36c63cdb98`)
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
@@ -89,7 +89,7 @@ export default function JudgeScoringPage() {
|
|||||||
const loadCompetitionRules = async () => {
|
const loadCompetitionRules = async () => {
|
||||||
try {
|
try {
|
||||||
// 使用正確的競賽ID
|
// 使用正確的競賽ID
|
||||||
const response = await fetch('/api/competitions/be47d842-91f1-11f0-8595-bd825523ae01/rules')
|
const response = await fetch('/api/competitions/0fffae9a-9539-11f0-b5d9-6e36c63cdb98/rules')
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
@@ -169,7 +169,7 @@ export default function JudgeScoringPage() {
|
|||||||
participantType: 'app',
|
participantType: 'app',
|
||||||
scores: scores,
|
scores: scores,
|
||||||
comments: comments.trim(),
|
comments: comments.trim(),
|
||||||
competitionId: 'be47d842-91f1-11f0-8595-bd825523ae01', // 正確的競賽ID
|
competitionId: '0fffae9a-9539-11f0-b5d9-6e36c63cdb98', // 正確的競賽ID
|
||||||
isEdit: selectedItem.status === "completed", // 如果是重新評分,標記為編輯模式
|
isEdit: selectedItem.status === "completed", // 如果是重新評分,標記為編輯模式
|
||||||
recordId: selectedItem.status === "completed" ? selectedItem.id : null
|
recordId: selectedItem.status === "completed" ? selectedItem.id : null
|
||||||
})
|
})
|
||||||
@@ -298,11 +298,6 @@ export default function JudgeScoringPage() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className="text-center text-sm text-gray-500">
|
|
||||||
<p>評審ID範例:j1, j2, j3, j4, j5</p>
|
|
||||||
<p>存取碼:judge2024</p>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -217,7 +217,6 @@ export default function AIShowcasePlatform() {
|
|||||||
if (statsResponse.ok) {
|
if (statsResponse.ok) {
|
||||||
const statsData = await statsResponse.json()
|
const statsData = await statsResponse.json()
|
||||||
if (statsData.success) {
|
if (statsData.success) {
|
||||||
console.log(`載入應用 ${app.name} 的統計數據:`, statsData.data)
|
|
||||||
return { ...app, ...statsData.data }
|
return { ...app, ...statsData.data }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -365,7 +364,6 @@ export default function AIShowcasePlatform() {
|
|||||||
const handleTryApp = async (appId: string) => {
|
const handleTryApp = async (appId: string) => {
|
||||||
await incrementViewCount(appId)
|
await incrementViewCount(appId)
|
||||||
addToRecentApps(appId)
|
addToRecentApps(appId)
|
||||||
console.log(`Opening app ${appId}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getCompetitionTypeIcon = (type: string) => {
|
const getCompetitionTypeIcon = (type: string) => {
|
||||||
|
@@ -14,7 +14,6 @@ export default function TestManualScoringPage() {
|
|||||||
|
|
||||||
const loadCompetitionData = async () => {
|
const loadCompetitionData = async () => {
|
||||||
try {
|
try {
|
||||||
console.log('🔍 開始載入競賽數據...')
|
|
||||||
|
|
||||||
// 載入競賽信息
|
// 載入競賽信息
|
||||||
const competitionResponse = await fetch('/api/competitions/be4b0a71-91f1-11f0-bb38-4adff2d0e33e')
|
const competitionResponse = await fetch('/api/competitions/be4b0a71-91f1-11f0-bb38-4adff2d0e33e')
|
||||||
@@ -22,7 +21,6 @@ export default function TestManualScoringPage() {
|
|||||||
|
|
||||||
if (competitionData.success) {
|
if (competitionData.success) {
|
||||||
setCompetition(competitionData.data.competition)
|
setCompetition(competitionData.data.competition)
|
||||||
console.log('✅ 競賽載入成功:', competitionData.data.competition.name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 載入團隊數據
|
// 載入團隊數據
|
||||||
@@ -31,15 +29,6 @@ export default function TestManualScoringPage() {
|
|||||||
|
|
||||||
if (teamsData.success) {
|
if (teamsData.success) {
|
||||||
setTeams(teamsData.data.teams)
|
setTeams(teamsData.data.teams)
|
||||||
console.log('✅ 團隊載入成功:', teamsData.data.teams.length, '個團隊')
|
|
||||||
teamsData.data.teams.forEach((team: any) => {
|
|
||||||
console.log(` - ${team.name}: ${team.apps?.length || 0} 個APP`)
|
|
||||||
if (team.apps && team.apps.length > 0) {
|
|
||||||
team.apps.forEach((app: any) => {
|
|
||||||
console.log(` * ${app.name} (${app.id})`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@@ -860,32 +860,11 @@ export function CompetitionManagement() {
|
|||||||
|
|
||||||
// Get participants based on competition type
|
// Get participants based on competition type
|
||||||
const getParticipants = (competitionType: string) => {
|
const getParticipants = (competitionType: string) => {
|
||||||
console.log('🔍 getParticipants 調用:', {
|
|
||||||
competitionType,
|
|
||||||
dbTeamsLength: dbTeams.length,
|
|
||||||
teamsLength: teams.length,
|
|
||||||
isLoadingTeams
|
|
||||||
})
|
|
||||||
switch (competitionType) {
|
switch (competitionType) {
|
||||||
case "individual":
|
case "individual":
|
||||||
console.log('🔍 個人賽APP數據:', {
|
|
||||||
availableAppsLength: availableApps.length,
|
|
||||||
availableApps: availableApps.slice(0, 2)
|
|
||||||
})
|
|
||||||
return availableApps
|
return availableApps
|
||||||
case "team":
|
case "team":
|
||||||
// 總是使用 dbTeams,如果為空則返回空數組
|
// 總是使用 dbTeams,如果為空則返回空數組
|
||||||
console.log('🔍 getParticipants 團隊數據:', {
|
|
||||||
dbTeamsLength: dbTeams.length,
|
|
||||||
dbTeams: dbTeams.slice(0, 2), // 只顯示前2個
|
|
||||||
firstTeam: dbTeams[0] ? {
|
|
||||||
id: dbTeams[0].id,
|
|
||||||
name: dbTeams[0].name,
|
|
||||||
leader_name: dbTeams[0].leader_name,
|
|
||||||
member_count: dbTeams[0].member_count,
|
|
||||||
submissionDate: dbTeams[0].submissionDate
|
|
||||||
} : null
|
|
||||||
})
|
|
||||||
return dbTeams
|
return dbTeams
|
||||||
default:
|
default:
|
||||||
return []
|
return []
|
||||||
@@ -898,12 +877,6 @@ export function CompetitionManagement() {
|
|||||||
let searchTerm = participantSearchTerm
|
let searchTerm = participantSearchTerm
|
||||||
let departmentFilterValue = departmentFilter
|
let departmentFilterValue = departmentFilter
|
||||||
|
|
||||||
console.log('🔍 getFilteredParticipants 調用:', {
|
|
||||||
competitionType,
|
|
||||||
participantsLength: participants.length,
|
|
||||||
searchTerm,
|
|
||||||
departmentFilterValue
|
|
||||||
})
|
|
||||||
|
|
||||||
// Use separate search terms for mixed competitions
|
// Use separate search terms for mixed competitions
|
||||||
if (newCompetition.type === "mixed") {
|
if (newCompetition.type === "mixed") {
|
||||||
@@ -5319,22 +5292,6 @@ export function CompetitionManagement() {
|
|||||||
) : (
|
) : (
|
||||||
getFilteredParticipants("team").map((participant) => {
|
getFilteredParticipants("team").map((participant) => {
|
||||||
const isSelected = newCompetition.participatingTeams.includes(participant.id)
|
const isSelected = newCompetition.participatingTeams.includes(participant.id)
|
||||||
console.log('🔍 團隊數據調試 - 完整對象:', participant)
|
|
||||||
console.log('🔍 團隊數據調試 - 關鍵欄位:', {
|
|
||||||
name: participant.name,
|
|
||||||
leader_name: participant.leader_name,
|
|
||||||
leader: participant.leader,
|
|
||||||
member_count: participant.member_count,
|
|
||||||
submissionDate: participant.submissionDate,
|
|
||||||
hasLeaderName: 'leader_name' in participant,
|
|
||||||
hasMemberCount: 'member_count' in participant,
|
|
||||||
allKeys: Object.keys(participant)
|
|
||||||
})
|
|
||||||
console.log('🔍 渲染測試:', {
|
|
||||||
leaderDisplay: participant.leader_name || participant.leader || '未知',
|
|
||||||
memberDisplay: participant.member_count || participant.memberCount || 0,
|
|
||||||
dateDisplay: participant.submissionDate ? new Date(participant.submissionDate).toLocaleDateString('zh-TW', { timeZone: 'Asia/Taipei', year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/-/g, '/') : '未知'
|
|
||||||
})
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={participant.id}
|
key={participant.id}
|
||||||
|
@@ -19,12 +19,14 @@ interface ScoringLinkDialogProps {
|
|||||||
export function ScoringLinkDialog({ open, onOpenChange, currentCompetition }: ScoringLinkDialogProps) {
|
export function ScoringLinkDialog({ open, onOpenChange, currentCompetition }: ScoringLinkDialogProps) {
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
|
|
||||||
// 生成評分連結URL
|
// 根據選擇的競賽生成評分連結URL
|
||||||
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || (typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000')
|
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || (typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000')
|
||||||
const scoringUrl = `${baseUrl}/judge-scoring`
|
const scoringUrl = currentCompetition?.id
|
||||||
|
? `${baseUrl}/judge-scoring?competitionId=${currentCompetition.id}`
|
||||||
|
: `${baseUrl}/judge-scoring`
|
||||||
|
|
||||||
const accessCode = "judge2024"
|
const accessCode = "judge2024"
|
||||||
const competitionName = currentCompetition?.name || "2024年第四季綜合AI競賽"
|
const competitionName = currentCompetition?.name || "未選擇競賽"
|
||||||
|
|
||||||
const handleCopyUrl = async () => {
|
const handleCopyUrl = async () => {
|
||||||
try {
|
try {
|
||||||
|
@@ -26,10 +26,11 @@ interface ScoringRecord {
|
|||||||
participantId: string
|
participantId: string
|
||||||
participantName: string
|
participantName: string
|
||||||
participantType: "individual" | "team"
|
participantType: "individual" | "team"
|
||||||
|
teamName?: string
|
||||||
scores: Record<string, number>
|
scores: Record<string, number>
|
||||||
totalScore: number
|
totalScore: number
|
||||||
comments: string
|
comments: string
|
||||||
submittedAt: string
|
submittedAt?: string
|
||||||
status: "completed" | "pending" | "draft"
|
status: "completed" | "pending" | "draft"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,8 +199,52 @@ export function ScoringManagement() {
|
|||||||
return totalWeight > 0 ? totalScore / totalWeight : 0
|
return totalWeight > 0 ? totalScore / totalWeight : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 生成所有評審和APP的組合
|
||||||
|
const generateAllScoringCombinations = () => {
|
||||||
|
if (!competitionJudges.length || !competitionParticipants.length) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const combinations: ScoringRecord[] = []
|
||||||
|
|
||||||
|
// 為每個評審和每個參賽者創建組合
|
||||||
|
competitionJudges.forEach(judge => {
|
||||||
|
competitionParticipants.forEach(participant => {
|
||||||
|
// 檢查是否已有評分記錄
|
||||||
|
const existingRecord = scoringRecords.find(record =>
|
||||||
|
record.judgeId === judge.id && record.participantId === participant.id
|
||||||
|
)
|
||||||
|
|
||||||
|
if (existingRecord) {
|
||||||
|
// 使用現有記錄
|
||||||
|
combinations.push(existingRecord)
|
||||||
|
} else {
|
||||||
|
// 創建新的待評分記錄
|
||||||
|
combinations.push({
|
||||||
|
id: `pending_${judge.id}_${participant.id}`,
|
||||||
|
judgeId: judge.id,
|
||||||
|
judgeName: judge.name,
|
||||||
|
participantId: participant.id,
|
||||||
|
participantName: participant.displayName || participant.name,
|
||||||
|
participantType: participant.type as "individual" | "team",
|
||||||
|
teamName: participant.teamName,
|
||||||
|
scores: {},
|
||||||
|
totalScore: 0,
|
||||||
|
comments: "",
|
||||||
|
status: "pending"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return combinations
|
||||||
|
}
|
||||||
|
|
||||||
const getFilteredRecords = () => {
|
const getFilteredRecords = () => {
|
||||||
let filtered = [...scoringRecords]
|
// 使用生成的組合而不是僅有的評分記錄
|
||||||
|
const allCombinations = generateAllScoringCombinations()
|
||||||
|
let filtered = [...allCombinations]
|
||||||
|
|
||||||
if (statusFilter !== "all") {
|
if (statusFilter !== "all") {
|
||||||
filtered = filtered.filter(record => record.status === statusFilter)
|
filtered = filtered.filter(record => record.status === statusFilter)
|
||||||
}
|
}
|
||||||
@@ -272,7 +317,13 @@ export function ScoringManagement() {
|
|||||||
scores: initialScores,
|
scores: initialScores,
|
||||||
comments: record.comments || '',
|
comments: record.comments || '',
|
||||||
})
|
})
|
||||||
setShowEditScoring(true)
|
|
||||||
|
// 如果是待評分項目,顯示手動評分對話框;如果是已完成項目,顯示編輯對話框
|
||||||
|
if (record.status === "pending") {
|
||||||
|
setShowManualScoring(true)
|
||||||
|
} else {
|
||||||
|
setShowEditScoring(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmitScore = async () => {
|
const handleSubmitScore = async () => {
|
||||||
@@ -344,7 +395,36 @@ export function ScoringManagement() {
|
|||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
setSuccess(showEditScoring ? "評分更新成功!" : "評分提交成功!")
|
setSuccess(showEditScoring ? "評分更新成功!" : "評分提交成功!")
|
||||||
await loadScoringData() // 重新載入數據
|
|
||||||
|
// 更新本地評分記錄
|
||||||
|
const newRecord: ScoringRecord = {
|
||||||
|
id: data.data?.id || `new_${Date.now()}`,
|
||||||
|
judgeId: manualScoring.judgeId,
|
||||||
|
judgeName: competitionJudges.find(j => j.id === manualScoring.judgeId)?.name || '未知評審',
|
||||||
|
participantId: manualScoring.participantId,
|
||||||
|
participantName: competitionParticipants.find(p => p.id === manualScoring.participantId)?.displayName || competitionParticipants.find(p => p.id === manualScoring.participantId)?.name || '未知參賽者',
|
||||||
|
participantType: competitionParticipants.find(p => p.id === manualScoring.participantId)?.type as "individual" | "team" || "individual",
|
||||||
|
scores: apiScores,
|
||||||
|
totalScore: calculateTotalScore(apiScores, rules),
|
||||||
|
comments: manualScoring.comments.trim(),
|
||||||
|
status: "completed",
|
||||||
|
submittedAt: new Date().toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新本地狀態
|
||||||
|
setScoringRecords(prev => {
|
||||||
|
const existingIndex = prev.findIndex(r => r.judgeId === newRecord.judgeId && r.participantId === newRecord.participantId)
|
||||||
|
if (existingIndex >= 0) {
|
||||||
|
// 更新現有記錄
|
||||||
|
const updated = [...prev]
|
||||||
|
updated[existingIndex] = newRecord
|
||||||
|
return updated
|
||||||
|
} else {
|
||||||
|
// 添加新記錄
|
||||||
|
return [...prev, newRecord]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
setShowManualScoring(false)
|
setShowManualScoring(false)
|
||||||
setShowEditScoring(false)
|
setShowEditScoring(false)
|
||||||
setSelectedRecord(null)
|
setSelectedRecord(null)
|
||||||
@@ -463,7 +543,7 @@ export function ScoringManagement() {
|
|||||||
name: app.name, // app 名稱
|
name: app.name, // app 名稱
|
||||||
type: 'team',
|
type: 'team',
|
||||||
teamName: team.name || '未知團隊', // 團隊名稱
|
teamName: team.name || '未知團隊', // 團隊名稱
|
||||||
displayName: `${team.name || '未知團隊'} - ${app.name}`, // 顯示名稱:團隊名稱 - app名稱
|
displayName: app.name, // 只顯示 app 名稱,團隊名稱通過 teamName 屬性獲取
|
||||||
creator: team.members && team.members.find((m: any) => m.role === '隊長')?.name || '未知隊長',
|
creator: team.members && team.members.find((m: any) => m.role === '隊長')?.name || '未知隊長',
|
||||||
teamId: team.id // 保存團隊 ID
|
teamId: team.id // 保存團隊 ID
|
||||||
})
|
})
|
||||||
@@ -583,13 +663,24 @@ export function ScoringManagement() {
|
|||||||
setAppScoringDetails(null)
|
setAppScoringDetails(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const progress = {
|
// 計算基於所有組合的統計數據
|
||||||
total: scoringStats.totalScores,
|
const calculateProgressStats = () => {
|
||||||
completed: scoringStats.completedScores,
|
const allCombinations = generateAllScoringCombinations()
|
||||||
pending: scoringStats.pendingScores,
|
const total = allCombinations.length
|
||||||
percentage: scoringStats.completionRate
|
const completed = allCombinations.filter(record => record.status === "completed").length
|
||||||
|
const pending = allCombinations.filter(record => record.status === "pending").length
|
||||||
|
const percentage = total > 0 ? Math.round((completed / total) * 100) : 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
total,
|
||||||
|
completed,
|
||||||
|
pending,
|
||||||
|
percentage
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const progress = calculateProgressStats()
|
||||||
|
|
||||||
// 顯示初始載入狀態
|
// 顯示初始載入狀態
|
||||||
if (isInitialLoading) {
|
if (isInitialLoading) {
|
||||||
return (
|
return (
|
||||||
@@ -677,7 +768,7 @@ export function ScoringManagement() {
|
|||||||
<CardContent className="p-4">
|
<CardContent className="p-4">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-2xl font-bold text-blue-600">
|
<p className="text-2xl font-bold text-blue-600">
|
||||||
{scoringSummary ? scoringSummary.overallStats.totalJudges : progress.completed}
|
{competitionJudges.length}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-gray-600">評審總數</p>
|
<p className="text-sm text-gray-600">評審總數</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -687,7 +778,7 @@ export function ScoringManagement() {
|
|||||||
<CardContent className="p-4">
|
<CardContent className="p-4">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-2xl font-bold text-green-600">
|
<p className="text-2xl font-bold text-green-600">
|
||||||
{scoringSummary ? scoringSummary.overallStats.totalApps : progress.pending}
|
{competitionParticipants.length}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-gray-600">參賽APP數</p>
|
<p className="text-sm text-gray-600">參賽APP數</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -697,7 +788,7 @@ export function ScoringManagement() {
|
|||||||
<CardContent className="p-4">
|
<CardContent className="p-4">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-2xl font-bold text-orange-600">
|
<p className="text-2xl font-bold text-orange-600">
|
||||||
{scoringSummary ? scoringSummary.overallStats.completedScores : progress.percentage}
|
{progress.completed}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-gray-600">已完成評分</p>
|
<p className="text-sm text-gray-600">已完成評分</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -707,7 +798,7 @@ export function ScoringManagement() {
|
|||||||
<CardContent className="p-4">
|
<CardContent className="p-4">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-2xl font-bold text-purple-600">
|
<p className="text-2xl font-bold text-purple-600">
|
||||||
{scoringSummary ? `${scoringSummary.overallStats.overallCompletionRate}%` : progress.total}
|
{progress.percentage}%
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-gray-600">總完成率</p>
|
<p className="text-sm text-gray-600">總完成率</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -718,15 +809,10 @@ export function ScoringManagement() {
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex justify-between text-sm">
|
<div className="flex justify-between text-sm">
|
||||||
<span>評分進度</span>
|
<span>評分進度</span>
|
||||||
<span>
|
<span>{progress.completed} / {progress.total}</span>
|
||||||
{scoringSummary ?
|
|
||||||
`${scoringSummary.overallStats.completedScores} / ${scoringSummary.overallStats.totalPossibleScores}` :
|
|
||||||
`${progress.completed} / ${progress.total}`
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<Progress
|
<Progress
|
||||||
value={scoringSummary ? scoringSummary.overallStats.overallCompletionRate : progress.percentage}
|
value={progress.percentage}
|
||||||
className="h-2"
|
className="h-2"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -876,7 +962,7 @@ export function ScoringManagement() {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
const container = document.getElementById(`scroll-${judgeId}`)
|
const container = document.getElementById(`scroll-${judgeId}`)
|
||||||
if (container) {
|
if (container) {
|
||||||
container.scrollLeft -= 280 // 滑動一個卡片的寬度
|
container.scrollLeft -= 304 // 滑動一個卡片的寬度 (288px + 16px間距)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="absolute -left-6 top-1/2 transform -translate-y-1/2 z-10 bg-white border border-gray-300 rounded-full p-3 shadow-lg hover:shadow-xl transition-all duration-200 hover:bg-gray-50"
|
className="absolute -left-6 top-1/2 transform -translate-y-1/2 z-10 bg-white border border-gray-300 rounded-full p-3 shadow-lg hover:shadow-xl transition-all duration-200 hover:bg-gray-50"
|
||||||
@@ -891,7 +977,7 @@ export function ScoringManagement() {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
const container = document.getElementById(`scroll-${judgeId}`)
|
const container = document.getElementById(`scroll-${judgeId}`)
|
||||||
if (container) {
|
if (container) {
|
||||||
container.scrollLeft += 280 // 滑動一個卡片的寬度
|
container.scrollLeft += 304 // 滑動一個卡片的寬度 (288px + 16px間距)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="absolute -right-6 top-1/2 transform -translate-y-1/2 z-10 bg-white border border-gray-300 rounded-full p-3 shadow-lg hover:shadow-xl transition-all duration-200 hover:bg-gray-50"
|
className="absolute -right-6 top-1/2 transform -translate-y-1/2 z-10 bg-white border border-gray-300 rounded-full p-3 shadow-lg hover:shadow-xl transition-all duration-200 hover:bg-gray-50"
|
||||||
@@ -906,26 +992,33 @@ export function ScoringManagement() {
|
|||||||
style={{
|
style={{
|
||||||
scrollbarWidth: 'none',
|
scrollbarWidth: 'none',
|
||||||
msOverflowStyle: 'none',
|
msOverflowStyle: 'none',
|
||||||
maxWidth: 'calc(4 * 256px + 3 * 16px)' // 4個卡片 + 3個間距
|
maxWidth: 'calc(4 * 288px + 3 * 16px)' // 4個卡片 + 3個間距
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{records.map((record) => (
|
{records.map((record) => (
|
||||||
<div
|
<div
|
||||||
key={record.id}
|
key={record.id}
|
||||||
className="flex-shrink-0 w-64 bg-white border rounded-lg p-4 shadow-sm hover:shadow-md transition-all duration-200"
|
className="flex-shrink-0 w-72 bg-white border rounded-lg p-4 shadow-sm hover:shadow-md transition-all duration-200"
|
||||||
>
|
>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{/* 項目標題和類型 */}
|
{/* 項目標題和類型 */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-start justify-between space-x-2">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-start space-x-2 flex-1 min-w-0">
|
||||||
{record.participantType === "individual" ? (
|
{record.participantType === "individual" ? (
|
||||||
<User className="w-4 h-4 text-blue-600" />
|
<User className="w-4 h-4 text-blue-600 flex-shrink-0 mt-0.5" />
|
||||||
) : (
|
) : (
|
||||||
<Users className="w-4 h-4 text-green-600" />
|
<Users className="w-4 h-4 text-green-600 flex-shrink-0 mt-0.5" />
|
||||||
)}
|
)}
|
||||||
<span className="font-medium text-sm truncate">{record.participantName}</span>
|
<div className="min-w-0 flex-1">
|
||||||
|
<span className="font-medium text-sm leading-tight break-words block">
|
||||||
|
{record.participantType === "team" && record.teamName
|
||||||
|
? `${record.teamName} - ${record.participantName}`
|
||||||
|
: record.participantName
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Badge variant="outline" className="text-xs">
|
<Badge variant="outline" className="text-xs flex-shrink-0">
|
||||||
{record.participantType === "individual" ? "個人" : "團隊"}
|
{record.participantType === "individual" ? "個人" : "團隊"}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -79,11 +79,9 @@ export function TeamManagement() {
|
|||||||
if (data.success) {
|
if (data.success) {
|
||||||
setApiTeams(data.data)
|
setApiTeams(data.data)
|
||||||
} else {
|
} else {
|
||||||
console.error('獲取團隊數據失敗:', data.message)
|
|
||||||
setError('獲取團隊數據失敗')
|
setError('獲取團隊數據失敗')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('獲取團隊數據失敗:', error)
|
|
||||||
setError('獲取團隊數據失敗')
|
setError('獲取團隊數據失敗')
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoadingTeams(false)
|
setIsLoadingTeams(false)
|
||||||
@@ -105,11 +103,9 @@ export function TeamManagement() {
|
|||||||
if (data.success) {
|
if (data.success) {
|
||||||
setAvailableUsers(data.data)
|
setAvailableUsers(data.data)
|
||||||
} else {
|
} else {
|
||||||
console.error('獲取用戶列表失敗:', data.message)
|
|
||||||
setError('獲取用戶列表失敗')
|
setError('獲取用戶列表失敗')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('獲取用戶列表失敗:', error)
|
|
||||||
setError('獲取用戶列表失敗')
|
setError('獲取用戶列表失敗')
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoadingUsers(false)
|
setIsLoadingUsers(false)
|
||||||
|
@@ -198,11 +198,8 @@ export function AppDetailDialog({ open, onOpenChange, app }: AppDetailDialogProp
|
|||||||
if (department) params.append('department', department)
|
if (department) params.append('department', department)
|
||||||
|
|
||||||
const url = `/api/apps/${app.id}/stats${params.toString() ? `?${params.toString()}` : ''}`
|
const url = `/api/apps/${app.id}/stats${params.toString() ? `?${params.toString()}` : ''}`
|
||||||
console.log('🔍 調用統計 API:', url)
|
|
||||||
console.log('🔍 應用 ID:', app.id)
|
|
||||||
|
|
||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
console.log('🔍 API 響應狀態:', response.status, response.statusText)
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error('❌ API 響應錯誤:', response.status, response.statusText)
|
console.error('❌ API 響應錯誤:', response.status, response.statusText)
|
||||||
@@ -210,10 +207,8 @@ export function AppDetailDialog({ open, onOpenChange, app }: AppDetailDialogProp
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
console.log('🔍 API 響應數據:', data)
|
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
console.log('📊 應用統計數據加載成功:', data.data)
|
|
||||||
setAppStats(data.data)
|
setAppStats(data.data)
|
||||||
setCurrentRating(data.data.basic.rating)
|
setCurrentRating(data.data.basic.rating)
|
||||||
setReviewCount(data.data.basic.reviewCount)
|
setReviewCount(data.data.basic.reviewCount)
|
||||||
@@ -272,14 +267,12 @@ export function AppDetailDialog({ open, onOpenChange, app }: AppDetailDialogProp
|
|||||||
}, [startDate, endDate, open, app.id, handleDateRangeChange])
|
}, [startDate, endDate, open, app.id, handleDateRangeChange])
|
||||||
|
|
||||||
const handleTryApp = async () => {
|
const handleTryApp = async () => {
|
||||||
console.log('handleTryApp 被調用', { user: user?.id, appId: app.id })
|
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
addToRecentApps(app.id.toString())
|
addToRecentApps(app.id.toString())
|
||||||
|
|
||||||
// 記錄用戶活動
|
// 記錄用戶活動
|
||||||
try {
|
try {
|
||||||
console.log('開始記錄用戶活動...')
|
|
||||||
const response = await fetch('/api/user/activity', {
|
const response = await fetch('/api/user/activity', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -298,7 +291,6 @@ export function AppDetailDialog({ open, onOpenChange, app }: AppDetailDialogProp
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
console.log('活動記錄成功')
|
|
||||||
} else {
|
} else {
|
||||||
console.error('活動記錄失敗:', response.status, response.statusText)
|
console.error('活動記錄失敗:', response.status, response.statusText)
|
||||||
}
|
}
|
||||||
@@ -306,7 +298,6 @@ export function AppDetailDialog({ open, onOpenChange, app }: AppDetailDialogProp
|
|||||||
console.error('記錄活動失敗:', error)
|
console.error('記錄活動失敗:', error)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('用戶未登入,跳過活動記錄')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increment view count when trying the app
|
// Increment view count when trying the app
|
||||||
|
@@ -59,7 +59,6 @@ export function CompetitionDetailDialog({
|
|||||||
const handleTryApp = (appId: string) => {
|
const handleTryApp = (appId: string) => {
|
||||||
incrementViewCount(appId)
|
incrementViewCount(appId)
|
||||||
addToRecentApps(appId)
|
addToRecentApps(appId)
|
||||||
console.log(`Opening app ${appId}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTypeColor = (type: string) => {
|
const getTypeColor = (type: string) => {
|
||||||
|
@@ -94,17 +94,14 @@ export function PopularityRankings() {
|
|||||||
if (appsData.success) {
|
if (appsData.success) {
|
||||||
// 合併個人應用和團隊應用
|
// 合併個人應用和團隊應用
|
||||||
const allApps = appsData.data.apps || []
|
const allApps = appsData.data.apps || []
|
||||||
console.log('📱 載入的應用數據:', allApps)
|
|
||||||
setCompetitionApps(allApps)
|
setCompetitionApps(allApps)
|
||||||
}
|
}
|
||||||
if (teamsData.success) {
|
if (teamsData.success) {
|
||||||
const teams = teamsData.data.teams || []
|
const teams = teamsData.data.teams || []
|
||||||
console.log('👥 載入的團隊數據:', teams)
|
|
||||||
setCompetitionTeams(teams)
|
setCompetitionTeams(teams)
|
||||||
}
|
}
|
||||||
if (judgesData.success) {
|
if (judgesData.success) {
|
||||||
const judges = judgesData.data.judges || []
|
const judges = judgesData.data.judges || []
|
||||||
console.log('👨⚖️ 載入的評審數據:', judges)
|
|
||||||
setCompetitionJudges(judges)
|
setCompetitionJudges(judges)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@@ -46,7 +46,6 @@ export function FavoritesPage() {
|
|||||||
}, [user])
|
}, [user])
|
||||||
|
|
||||||
const handleUseApp = async (app: any) => {
|
const handleUseApp = async (app: any) => {
|
||||||
console.log('handleUseApp 被調用', { user: user?.id, appId: app.id, appName: app.name })
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Increment view count when using the app
|
// Increment view count when using the app
|
||||||
@@ -65,7 +64,6 @@ export function FavoritesPage() {
|
|||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
// 記錄用戶活動
|
// 記錄用戶活動
|
||||||
try {
|
try {
|
||||||
console.log('開始記錄用戶活動...')
|
|
||||||
const activityResponse = await fetch('/api/user/activity', {
|
const activityResponse = await fetch('/api/user/activity', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -84,7 +82,6 @@ export function FavoritesPage() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (activityResponse.ok) {
|
if (activityResponse.ok) {
|
||||||
console.log('活動記錄成功')
|
|
||||||
} else {
|
} else {
|
||||||
console.error('活動記錄失敗:', activityResponse.status, activityResponse.statusText)
|
console.error('活動記錄失敗:', activityResponse.status, activityResponse.statusText)
|
||||||
}
|
}
|
||||||
@@ -98,7 +95,6 @@ export function FavoritesPage() {
|
|||||||
console.error('增加查看次數失敗:', response.status, response.statusText)
|
console.error('增加查看次數失敗:', response.status, response.statusText)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('用戶未登入,跳過活動記錄')
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('增加查看次數失敗:', error)
|
console.error('增加查看次數失敗:', error)
|
||||||
@@ -109,7 +105,6 @@ export function FavoritesPage() {
|
|||||||
const url = app.appUrl.startsWith('http') ? app.appUrl : `https://${app.appUrl}`
|
const url = app.appUrl.startsWith('http') ? app.appUrl : `https://${app.appUrl}`
|
||||||
window.open(url, "_blank", "noopener,noreferrer")
|
window.open(url, "_blank", "noopener,noreferrer")
|
||||||
} else {
|
} else {
|
||||||
console.log(`Opening app: ${app.name}`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -353,7 +353,6 @@ export function ReviewSystem({ appId, appName, currentRating, onRatingUpdate }:
|
|||||||
{[5, 4, 3, 2, 1].map((rating) => {
|
{[5, 4, 3, 2, 1].map((rating) => {
|
||||||
const count = reviews.filter((r) => r.rating === rating).length
|
const count = reviews.filter((r) => r.rating === rating).length
|
||||||
const percentage = (count / reviews.length) * 100
|
const percentage = (count / reviews.length) * 100
|
||||||
console.log(`評分 ${rating}: count=${count}, percentage=${percentage}%`) // 調試信息
|
|
||||||
return (
|
return (
|
||||||
<div key={rating} className="flex items-center space-x-3">
|
<div key={rating} className="flex items-center space-x-3">
|
||||||
<div className="flex items-center space-x-1 w-12">
|
<div className="flex items-center space-x-1 w-12">
|
||||||
|
@@ -126,12 +126,10 @@ export function CompetitionProvider({ children }: { children: ReactNode }) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadCompetitions = async () => {
|
const loadCompetitions = async () => {
|
||||||
try {
|
try {
|
||||||
console.log('🔄 開始載入競賽數據...')
|
|
||||||
|
|
||||||
// 載入所有競賽
|
// 載入所有競賽
|
||||||
const competitionsResponse = await fetch('/api/competitions')
|
const competitionsResponse = await fetch('/api/competitions')
|
||||||
const competitionsData = await competitionsResponse.json()
|
const competitionsData = await competitionsResponse.json()
|
||||||
console.log('📋 競賽API回應:', competitionsData)
|
|
||||||
|
|
||||||
if (competitionsData.success) {
|
if (competitionsData.success) {
|
||||||
if (competitionsData.data && competitionsData.data.length > 0) {
|
if (competitionsData.data && competitionsData.data.length > 0) {
|
||||||
@@ -141,22 +139,18 @@ export function CompetitionProvider({ children }: { children: ReactNode }) {
|
|||||||
judges: comp.judges || []
|
judges: comp.judges || []
|
||||||
}))
|
}))
|
||||||
setCompetitions(competitionsWithJudges)
|
setCompetitions(competitionsWithJudges)
|
||||||
console.log('✅ 競賽數據載入成功:', competitionsWithJudges.length, '個競賽')
|
|
||||||
} else {
|
} else {
|
||||||
// 沒有競賽資料是正常情況,不報錯
|
// 沒有競賽資料是正常情況,不報錯
|
||||||
setCompetitions([])
|
setCompetitions([])
|
||||||
console.log('ℹ️ 暫無競賽數據')
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 沒有競賽數據是正常情況,不報錯
|
// 沒有競賽數據是正常情況,不報錯
|
||||||
setCompetitions([])
|
setCompetitions([])
|
||||||
console.log('ℹ️ 暫無競賽數據')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 載入當前競賽
|
// 載入當前競賽
|
||||||
const currentResponse = await fetch('/api/competitions/current')
|
const currentResponse = await fetch('/api/competitions/current')
|
||||||
const currentData = await currentResponse.json()
|
const currentData = await currentResponse.json()
|
||||||
console.log('🏆 當前競賽API回應:', currentData)
|
|
||||||
|
|
||||||
if (currentData.success) {
|
if (currentData.success) {
|
||||||
if (currentData.data) {
|
if (currentData.data) {
|
||||||
@@ -166,36 +160,28 @@ export function CompetitionProvider({ children }: { children: ReactNode }) {
|
|||||||
judges: currentData.data.judges || []
|
judges: currentData.data.judges || []
|
||||||
}
|
}
|
||||||
setCurrentCompetition(currentCompetitionWithJudges)
|
setCurrentCompetition(currentCompetitionWithJudges)
|
||||||
console.log('✅ 當前競賽載入成功:', currentCompetitionWithJudges.name)
|
|
||||||
} else {
|
} else {
|
||||||
// 沒有當前競賽是正常情況,不報錯
|
// 沒有當前競賽是正常情況,不報錯
|
||||||
setCurrentCompetition(null)
|
setCurrentCompetition(null)
|
||||||
console.log('ℹ️ 暫無當前競賽')
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 沒有當前競賽是正常情況,不報錯
|
// 沒有當前競賽是正常情況,不報錯
|
||||||
setCurrentCompetition(null)
|
setCurrentCompetition(null)
|
||||||
console.log('ℹ️ 暫無當前競賽')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 載入評審數據
|
// 載入評審數據
|
||||||
console.log('👨⚖️ 開始載入評審數據...')
|
|
||||||
const judgesResponse = await fetch('/api/admin/judges')
|
const judgesResponse = await fetch('/api/admin/judges')
|
||||||
const judgesData = await judgesResponse.json()
|
const judgesData = await judgesResponse.json()
|
||||||
console.log('評審API回應:', judgesData)
|
|
||||||
|
|
||||||
if (judgesData.success) {
|
if (judgesData.success) {
|
||||||
if (judgesData.data && judgesData.data.length > 0) {
|
if (judgesData.data && judgesData.data.length > 0) {
|
||||||
setJudges(judgesData.data)
|
setJudges(judgesData.data)
|
||||||
console.log('✅ 評審數據載入成功:', judgesData.data.length, '個評審')
|
|
||||||
} else {
|
} else {
|
||||||
setJudges([])
|
setJudges([])
|
||||||
console.log('ℹ️ 暫無評審數據')
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 沒有評審數據是正常情況,不報錯
|
// 沒有評審數據是正常情況,不報錯
|
||||||
setJudges([])
|
setJudges([])
|
||||||
console.log('ℹ️ 暫無評審數據')
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ 載入競賽數據失敗:', error)
|
console.error('❌ 載入競賽數據失敗:', error)
|
||||||
@@ -283,12 +269,8 @@ export function CompetitionProvider({ children }: { children: ReactNode }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deleteJudge = (id: string) => {
|
const deleteJudge = (id: string) => {
|
||||||
console.log('🗑️ Context deleteJudge 被調用,ID:', id)
|
|
||||||
console.log('🗑️ 刪除前的 judges 狀態:', judges.map(j => ({ id: j.id, name: j.name, is_active: j.is_active })))
|
|
||||||
|
|
||||||
setJudges((prev) => {
|
setJudges((prev) => {
|
||||||
const filtered = prev.filter((judge) => judge.id !== id)
|
const filtered = prev.filter((judge) => judge.id !== id)
|
||||||
console.log('🗑️ 刪除後的 judges 狀態:', filtered.map(j => ({ id: j.id, name: j.name, is_active: j.is_active })))
|
|
||||||
return filtered
|
return filtered
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@@ -613,14 +613,41 @@ export class UserService extends DatabaseServiceBase {
|
|||||||
`;
|
`;
|
||||||
const reviewStats = await this.queryOne(reviewStatsSql);
|
const reviewStats = await this.queryOne(reviewStatsSql);
|
||||||
|
|
||||||
// 競賽統計
|
// 競賽統計 - 使用動態計算的狀態
|
||||||
const competitionStatsSql = `
|
const competitions = await CompetitionService.getAllCompetitions();
|
||||||
SELECT
|
|
||||||
COUNT(*) as total_competitions,
|
// 動態計算每個競賽的狀態
|
||||||
COUNT(CASE WHEN status = 'active' OR status = 'ongoing' THEN 1 END) as active_competitions
|
const now = new Date();
|
||||||
FROM competitions
|
const competitionsWithCalculatedStatus = competitions.map(competition => {
|
||||||
`;
|
const startDate = new Date(competition.start_date);
|
||||||
const competitionStats = await this.queryOne(competitionStatsSql);
|
const endDate = new Date(competition.end_date);
|
||||||
|
|
||||||
|
let calculatedStatus = competition.status;
|
||||||
|
|
||||||
|
// 確保日期比較的準確性,使用 UTC 時間避免時區問題
|
||||||
|
const nowUTC = new Date(now.getTime() + now.getTimezoneOffset() * 60000);
|
||||||
|
const startDateUTC = new Date(startDate.getTime() + startDate.getTimezoneOffset() * 60000);
|
||||||
|
const endDateUTC = new Date(endDate.getTime() + endDate.getTimezoneOffset() * 60000);
|
||||||
|
|
||||||
|
// 根據實際日期計算狀態
|
||||||
|
if (nowUTC < startDateUTC) {
|
||||||
|
calculatedStatus = 'upcoming'; // 即將開始
|
||||||
|
} else if (nowUTC >= startDateUTC && nowUTC <= endDateUTC) {
|
||||||
|
calculatedStatus = 'active'; // 進行中
|
||||||
|
} else if (nowUTC > endDateUTC) {
|
||||||
|
calculatedStatus = 'completed'; // 已完成
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...competition,
|
||||||
|
status: calculatedStatus
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const competitionStats = {
|
||||||
|
total_competitions: competitionsWithCalculatedStatus.length,
|
||||||
|
active_competitions: competitionsWithCalculatedStatus.filter(c => c.status === 'active').length
|
||||||
|
};
|
||||||
|
|
||||||
// 計算增長率(與上個月比較)
|
// 計算增長率(與上個月比較)
|
||||||
const lastMonthUsersSql = `
|
const lastMonthUsersSql = `
|
||||||
@@ -903,7 +930,6 @@ export class JudgeService extends DatabaseServiceBase {
|
|||||||
const fields = Object.keys(updates).filter(key => key !== 'id' && key !== 'created_at');
|
const fields = Object.keys(updates).filter(key => key !== 'id' && key !== 'created_at');
|
||||||
|
|
||||||
if (fields.length === 0) {
|
if (fields.length === 0) {
|
||||||
console.log('沒有字段需要更新');
|
|
||||||
return true; // 沒有需要更新的字段,視為成功
|
return true; // 沒有需要更新的字段,視為成功
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -916,11 +942,7 @@ export class JudgeService extends DatabaseServiceBase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const sql = `UPDATE judges SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`;
|
const sql = `UPDATE judges SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`;
|
||||||
console.log('執行 SQL:', sql);
|
|
||||||
console.log('參數:', [...values, id]);
|
|
||||||
|
|
||||||
const result = await DatabaseServiceBase.safeUpdate(sql, [...values, id]);
|
const result = await DatabaseServiceBase.safeUpdate(sql, [...values, id]);
|
||||||
console.log('更新結果:', result);
|
|
||||||
return result.affectedRows > 0;
|
return result.affectedRows > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -938,38 +960,59 @@ export class JudgeService extends DatabaseServiceBase {
|
|||||||
|
|
||||||
// 獲取評審的評分任務
|
// 獲取評審的評分任務
|
||||||
static async getJudgeScoringTasks(judgeId: string, competitionId?: string): Promise<any[]> {
|
static async getJudgeScoringTasks(judgeId: string, competitionId?: string): Promise<any[]> {
|
||||||
let sql = `
|
let sql: string;
|
||||||
SELECT DISTINCT
|
let params: any[];
|
||||||
a.id,
|
|
||||||
a.name,
|
|
||||||
'app' as type,
|
|
||||||
'individual' as participant_type,
|
|
||||||
COALESCE(js.total_score, 0) as score,
|
|
||||||
CASE
|
|
||||||
WHEN js.total_score > 0 THEN 'completed'
|
|
||||||
ELSE 'pending'
|
|
||||||
END as status,
|
|
||||||
js.submitted_at,
|
|
||||||
t.name as team_name,
|
|
||||||
CONCAT(COALESCE(t.name, '未知團隊'), ' - ', a.name) as display_name
|
|
||||||
FROM apps a
|
|
||||||
LEFT JOIN teams t ON a.team_id = t.id
|
|
||||||
LEFT JOIN competition_apps ca ON a.id = ca.app_id
|
|
||||||
LEFT JOIN judge_scores js ON a.id = js.app_id AND js.judge_id = ?
|
|
||||||
WHERE ca.competition_id = ?
|
|
||||||
`;
|
|
||||||
|
|
||||||
const params = [judgeId];
|
|
||||||
|
|
||||||
if (competitionId) {
|
if (competitionId) {
|
||||||
params.push(competitionId);
|
// 獲取特定競賽的評分任務
|
||||||
|
sql = `
|
||||||
|
SELECT DISTINCT
|
||||||
|
a.id,
|
||||||
|
a.name,
|
||||||
|
'app' as type,
|
||||||
|
'individual' as participant_type,
|
||||||
|
COALESCE(js.total_score, 0) as score,
|
||||||
|
CASE
|
||||||
|
WHEN js.total_score > 0 THEN 'completed'
|
||||||
|
ELSE 'pending'
|
||||||
|
END as status,
|
||||||
|
js.submitted_at,
|
||||||
|
t.name as team_name,
|
||||||
|
a.name as display_name
|
||||||
|
FROM apps a
|
||||||
|
LEFT JOIN teams t ON a.team_id = t.id
|
||||||
|
LEFT JOIN competition_apps ca ON a.id = ca.app_id
|
||||||
|
LEFT JOIN judge_scores js ON a.id = js.app_id AND js.judge_id = ?
|
||||||
|
WHERE ca.competition_id = ?
|
||||||
|
ORDER BY a.name
|
||||||
|
`;
|
||||||
|
params = [judgeId, competitionId];
|
||||||
} else {
|
} else {
|
||||||
// 如果沒有指定競賽,獲取所有競賽的任務
|
// 獲取所有競賽的任務
|
||||||
sql = sql.replace('WHERE ca.competition_id = ?', 'WHERE ca.competition_id IS NOT NULL');
|
sql = `
|
||||||
|
SELECT DISTINCT
|
||||||
|
a.id,
|
||||||
|
a.name,
|
||||||
|
'app' as type,
|
||||||
|
'individual' as participant_type,
|
||||||
|
COALESCE(js.total_score, 0) as score,
|
||||||
|
CASE
|
||||||
|
WHEN js.total_score > 0 THEN 'completed'
|
||||||
|
ELSE 'pending'
|
||||||
|
END as status,
|
||||||
|
js.submitted_at,
|
||||||
|
t.name as team_name,
|
||||||
|
a.name as display_name
|
||||||
|
FROM apps a
|
||||||
|
LEFT JOIN teams t ON a.team_id = t.id
|
||||||
|
LEFT JOIN competition_apps ca ON a.id = ca.app_id
|
||||||
|
LEFT JOIN judge_scores js ON a.id = js.app_id AND js.judge_id = ?
|
||||||
|
WHERE ca.competition_id IS NOT NULL
|
||||||
|
ORDER BY a.name
|
||||||
|
`;
|
||||||
|
params = [judgeId];
|
||||||
}
|
}
|
||||||
|
|
||||||
sql += ' ORDER BY a.name';
|
|
||||||
|
|
||||||
const results = await DatabaseServiceBase.safeQuery(sql, params);
|
const results = await DatabaseServiceBase.safeQuery(sql, params);
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
@@ -1002,7 +1045,6 @@ export class TeamService extends DatabaseServiceBase {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const result = await DatabaseServiceBase.safeInsert(sql, params);
|
const result = await DatabaseServiceBase.safeInsert(sql, params);
|
||||||
console.log('團隊創建結果:', result);
|
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1025,13 +1067,6 @@ export class TeamService extends DatabaseServiceBase {
|
|||||||
ORDER BY t.created_at DESC
|
ORDER BY t.created_at DESC
|
||||||
`;
|
`;
|
||||||
const results = await DatabaseServiceBase.safeQuery(sql);
|
const results = await DatabaseServiceBase.safeQuery(sql);
|
||||||
console.log('🔍 getAllTeams 查詢結果:', results.slice(0, 2).map(r => ({
|
|
||||||
id: r.id,
|
|
||||||
name: r.name,
|
|
||||||
leader_name: r.leader_name,
|
|
||||||
member_count: r.member_count,
|
|
||||||
submissionDate: r.submissionDate
|
|
||||||
})));
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1291,10 +1326,7 @@ export class TeamService extends DatabaseServiceBase {
|
|||||||
WHERE a.team_id = ? AND a.is_active = 1
|
WHERE a.team_id = ? AND a.is_active = 1
|
||||||
ORDER BY a.created_at DESC
|
ORDER BY a.created_at DESC
|
||||||
`;
|
`;
|
||||||
console.log('📝 getTeamApps SQL:', sql);
|
|
||||||
console.log('📝 getTeamApps 參數:', [teamId]);
|
|
||||||
const results = await DatabaseServiceBase.safeQuery(sql, [teamId]);
|
const results = await DatabaseServiceBase.safeQuery(sql, [teamId]);
|
||||||
console.log('📊 getTeamApps 結果:', results.length, '個應用');
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1933,15 +1965,6 @@ export class CompetitionService extends DatabaseServiceBase {
|
|||||||
const startDateUTC = new Date(startDate.getTime() + startDate.getTimezoneOffset() * 60000);
|
const startDateUTC = new Date(startDate.getTime() + startDate.getTimezoneOffset() * 60000);
|
||||||
const endDateUTC = new Date(endDate.getTime() + endDate.getTimezoneOffset() * 60000);
|
const endDateUTC = new Date(endDate.getTime() + endDate.getTimezoneOffset() * 60000);
|
||||||
|
|
||||||
console.log('🔍 競賽狀態計算:', {
|
|
||||||
competitionId,
|
|
||||||
name: competition.name,
|
|
||||||
now: nowUTC.toISOString(),
|
|
||||||
startDate: startDateUTC.toISOString(),
|
|
||||||
endDate: endDateUTC.toISOString(),
|
|
||||||
originalStatus: competition.status
|
|
||||||
});
|
|
||||||
|
|
||||||
// 根據實際日期計算狀態
|
// 根據實際日期計算狀態
|
||||||
if (nowUTC < startDateUTC) {
|
if (nowUTC < startDateUTC) {
|
||||||
calculatedStatus = 'upcoming'; // 即將開始
|
calculatedStatus = 'upcoming'; // 即將開始
|
||||||
@@ -1951,8 +1974,6 @@ export class CompetitionService extends DatabaseServiceBase {
|
|||||||
calculatedStatus = 'completed'; // 已完成
|
calculatedStatus = 'completed'; // 已完成
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('🔍 計算後的狀態:', calculatedStatus);
|
|
||||||
|
|
||||||
// 轉換字段名稱以匹配前端期望的格式
|
// 轉換字段名稱以匹配前端期望的格式
|
||||||
return {
|
return {
|
||||||
...competition,
|
...competition,
|
||||||
@@ -2977,14 +2998,41 @@ export class AppService extends DatabaseServiceBase {
|
|||||||
`;
|
`;
|
||||||
const reviewStats = await this.queryOne(reviewStatsSql);
|
const reviewStats = await this.queryOne(reviewStatsSql);
|
||||||
|
|
||||||
// 競賽統計
|
// 競賽統計 - 使用動態計算的狀態
|
||||||
const competitionStatsSql = `
|
const competitions = await CompetitionService.getAllCompetitions();
|
||||||
SELECT
|
|
||||||
COUNT(*) as total_competitions,
|
// 動態計算每個競賽的狀態
|
||||||
COUNT(CASE WHEN status = 'active' OR status = 'ongoing' THEN 1 END) as active_competitions
|
const now = new Date();
|
||||||
FROM competitions
|
const competitionsWithCalculatedStatus = competitions.map(competition => {
|
||||||
`;
|
const startDate = new Date(competition.start_date);
|
||||||
const competitionStats = await this.queryOne(competitionStatsSql);
|
const endDate = new Date(competition.end_date);
|
||||||
|
|
||||||
|
let calculatedStatus = competition.status;
|
||||||
|
|
||||||
|
// 確保日期比較的準確性,使用 UTC 時間避免時區問題
|
||||||
|
const nowUTC = new Date(now.getTime() + now.getTimezoneOffset() * 60000);
|
||||||
|
const startDateUTC = new Date(startDate.getTime() + startDate.getTimezoneOffset() * 60000);
|
||||||
|
const endDateUTC = new Date(endDate.getTime() + endDate.getTimezoneOffset() * 60000);
|
||||||
|
|
||||||
|
// 根據實際日期計算狀態
|
||||||
|
if (nowUTC < startDateUTC) {
|
||||||
|
calculatedStatus = 'upcoming'; // 即將開始
|
||||||
|
} else if (nowUTC >= startDateUTC && nowUTC <= endDateUTC) {
|
||||||
|
calculatedStatus = 'active'; // 進行中
|
||||||
|
} else if (nowUTC > endDateUTC) {
|
||||||
|
calculatedStatus = 'completed'; // 已完成
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...competition,
|
||||||
|
status: calculatedStatus
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const competitionStats = {
|
||||||
|
total_competitions: competitionsWithCalculatedStatus.length,
|
||||||
|
active_competitions: competitionsWithCalculatedStatus.filter(c => c.status === 'active').length
|
||||||
|
};
|
||||||
|
|
||||||
// 計算增長率(與上個月比較)
|
// 計算增長率(與上個月比較)
|
||||||
const lastMonthUsersSql = `
|
const lastMonthUsersSql = `
|
||||||
@@ -3004,8 +3052,6 @@ export class AppService extends DatabaseServiceBase {
|
|||||||
totalUsers: userStats.totalUsers,
|
totalUsers: userStats.totalUsers,
|
||||||
activeUsers: userStats.activeUsers,
|
activeUsers: userStats.activeUsers,
|
||||||
totalApps: appStats?.total_apps || 0,
|
totalApps: appStats?.total_apps || 0,
|
||||||
activeApps: appStats?.active_apps || 0,
|
|
||||||
inactiveApps: appStats?.inactive_apps || 0,
|
|
||||||
totalCompetitions: competitionStats?.total_competitions || 0,
|
totalCompetitions: competitionStats?.total_competitions || 0,
|
||||||
totalReviews: reviewStats?.total_reviews || 0,
|
totalReviews: reviewStats?.total_reviews || 0,
|
||||||
totalViews: appStats?.total_views || 0,
|
totalViews: appStats?.total_views || 0,
|
||||||
@@ -3578,7 +3624,6 @@ export class ScoringService extends DatabaseServiceBase {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await DatabaseServiceBase.safeQuery(sql, [competitionId]);
|
const result = await DatabaseServiceBase.safeQuery(sql, [competitionId]);
|
||||||
console.log('🔍 競賽規則查詢結果:', result);
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ 獲取競賽規則失敗:', error);
|
console.error('❌ 獲取競賽規則失敗:', error);
|
||||||
@@ -3867,7 +3912,6 @@ export class ScoringService extends DatabaseServiceBase {
|
|||||||
};
|
};
|
||||||
}> {
|
}> {
|
||||||
try {
|
try {
|
||||||
console.log('🔍 獲取評分完成度匯總,competitionId:', competitionId);
|
|
||||||
|
|
||||||
// 獲取競賽的評審列表 - 先嘗試從關聯表獲取,如果沒有則獲取所有評審
|
// 獲取競賽的評審列表 - 先嘗試從關聯表獲取,如果沒有則獲取所有評審
|
||||||
let judgesResult = await DatabaseServiceBase.safeQuery(`
|
let judgesResult = await DatabaseServiceBase.safeQuery(`
|
||||||
|
Reference in New Issue
Block a user