From 049b53fa43865e12aab315df80e9aaeb3ac2dc98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B3=E4=BD=A9=E5=BA=AD?= Date: Sat, 20 Sep 2025 22:44:21 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E7=AB=B6=E8=B3=BD=E5=9C=98?= =?UTF-8?q?=E9=9A=8A=E7=B7=A8=E8=BC=AF=E3=80=81=E5=80=8B=E4=BA=BA=E8=B3=BD?= =?UTF-8?q?=E9=A1=AF=E7=A4=BA=E3=80=81=E5=9C=98=E9=AB=94=E8=B3=BD=E9=A1=AF?= =?UTF-8?q?=E7=A4=BA=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/admin/apps/available/route.ts | 18 ++- app/api/admin/competitions/stats/route.ts | 52 +++++-- components/admin/competition-management.tsx | 113 ++++++++++++--- components/admin/team-management.tsx | 136 +++++++++++++----- components/auth/profile-dialog.tsx | 2 +- .../competition/award-detail-dialog.tsx | 4 +- .../competition/competition-detail-dialog.tsx | 8 +- .../competition/judge-scoring-dialog.tsx | 2 +- .../competition/popularity-rankings.tsx | 2 +- components/competition/team-detail-dialog.tsx | 2 +- lib/services/database-service.ts | 23 ++- scripts/insert-team-members.sql | 50 +++++++ scripts/insert-test-team-members.js | 82 +++++++++++ scripts/test-team-data.js | 70 +++++++++ types/competition.ts | 1 + 15 files changed, 480 insertions(+), 85 deletions(-) create mode 100644 scripts/insert-team-members.sql create mode 100644 scripts/insert-test-team-members.js create mode 100644 scripts/test-team-data.js diff --git a/app/api/admin/apps/available/route.ts b/app/api/admin/apps/available/route.ts index c2c5e0f..0fc2c50 100644 --- a/app/api/admin/apps/available/route.ts +++ b/app/api/admin/apps/available/route.ts @@ -52,11 +52,15 @@ export async function GET(request: NextRequest) { // 獲取所有活躍的應用,編輯團隊時顯示所有應用(包括已綁定的) // 使用 is_active = 1 因為數據庫中存儲的是數字 1 + // 與 users 表 JOIN 獲取創建者姓名 let sql = ` - SELECT id, name, description, category, type, icon, icon_color, app_url, creator_id, team_id - FROM apps - WHERE is_active = 1 - ORDER BY created_at DESC + SELECT a.id, a.name, a.description, a.category, a.type, a.icon, a.icon_color, a.app_url, + a.creator_id, a.team_id, a.created_at as submissionDate, + u.name as creator_name + FROM apps a + LEFT JOIN users u ON a.creator_id = u.id + WHERE a.is_active = 1 + ORDER BY a.created_at DESC `; const params: any[] = []; @@ -72,17 +76,17 @@ export async function GET(request: NextRequest) { console.log('⚠️ 沒有找到 is_active = 1 的應用,嘗試其他查詢條件...'); // 嘗試 is_active = TRUE - const sqlTrue = sql.replace('WHERE is_active = 1', 'WHERE is_active = TRUE'); + const sqlTrue = sql.replace('WHERE a.is_active = 1', 'WHERE a.is_active = TRUE'); const appsTrue = await db.query(sqlTrue, params); console.log('📊 is_active = TRUE 查詢結果:', appsTrue.length, '個應用'); // 嘗試 is_active = '1' - const sqlString = sql.replace('WHERE is_active = 1', 'WHERE is_active = "1"'); + const sqlString = sql.replace('WHERE a.is_active = 1', 'WHERE a.is_active = "1"'); const appsString = await db.query(sqlString, params); console.log('📊 is_active = "1" 查詢結果:', appsString.length, '個應用'); // 嘗試沒有 is_active 條件 - const sqlNoFilter = sql.replace('WHERE 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); console.log('📊 無 is_active 過濾查詢結果:', appsNoFilter.length, '個應用'); diff --git a/app/api/admin/competitions/stats/route.ts b/app/api/admin/competitions/stats/route.ts index b540e4c..938cc7f 100644 --- a/app/api/admin/competitions/stats/route.ts +++ b/app/api/admin/competitions/stats/route.ts @@ -10,20 +10,48 @@ export async function GET(request: NextRequest) { try { const competitions = await CompetitionService.getAllCompetitions(); + // 動態計算每個競賽的狀態 + const now = new Date(); + const competitionsWithCalculatedStatus = competitions.map(competition => { + const startDate = new Date(competition.start_date); + 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 stats = { - total: competitions.length, - upcoming: competitions.filter(c => c.status === 'upcoming').length, - active: competitions.filter(c => c.status === 'active' || c.status === 'ongoing').length, - ongoing: competitions.filter(c => c.status === 'ongoing').length, - judging: competitions.filter(c => c.status === 'judging').length, - completed: competitions.filter(c => c.status === 'completed').length, - individual: competitions.filter(c => c.type === 'individual').length, - team: competitions.filter(c => c.type === 'team').length, - mixed: competitions.filter(c => c.type === 'mixed').length, - proposal: competitions.filter(c => c.type === 'proposal').length, - currentYear: competitions.filter(c => c.year === new Date().getFullYear()).length, - thisMonth: competitions.filter(c => + total: competitionsWithCalculatedStatus.length, + upcoming: competitionsWithCalculatedStatus.filter(c => c.status === 'upcoming').length, + active: competitionsWithCalculatedStatus.filter(c => c.status === 'active' || c.status === 'ongoing').length, + ongoing: competitionsWithCalculatedStatus.filter(c => c.status === 'ongoing').length, + judging: competitionsWithCalculatedStatus.filter(c => c.status === 'judging').length, + completed: competitionsWithCalculatedStatus.filter(c => c.status === 'completed').length, + individual: competitionsWithCalculatedStatus.filter(c => c.type === 'individual').length, + team: competitionsWithCalculatedStatus.filter(c => c.type === 'team').length, + mixed: competitionsWithCalculatedStatus.filter(c => c.type === 'mixed').length, + proposal: competitionsWithCalculatedStatus.filter(c => c.type === 'proposal').length, + currentYear: competitionsWithCalculatedStatus.filter(c => c.year === new Date().getFullYear()).length, + thisMonth: competitionsWithCalculatedStatus.filter(c => c.year === new Date().getFullYear() && c.month === new Date().getMonth() + 1 ).length diff --git a/components/admin/competition-management.tsx b/components/admin/competition-management.tsx index 4c81222..63800af 100644 --- a/components/admin/competition-management.tsx +++ b/components/admin/competition-management.tsx @@ -703,6 +703,7 @@ export function CompetitionManagement() { fetchJudgeStats() fetchTeams() fetchTeamStats() + fetchAvailableApps() }, []) // 當競賽列表載入完成後,載入每個競賽的評分進度 @@ -859,11 +860,33 @@ export function CompetitionManagement() { // Get participants based on competition type const getParticipants = (competitionType: string) => { + console.log('🔍 getParticipants 調用:', { + competitionType, + dbTeamsLength: dbTeams.length, + teamsLength: teams.length, + isLoadingTeams + }) switch (competitionType) { case "individual": - return mockIndividualApps + console.log('🔍 個人賽APP數據:', { + availableAppsLength: availableApps.length, + availableApps: availableApps.slice(0, 2) + }) + return availableApps case "team": - return dbTeams.length > 0 ? dbTeams : teams + // 總是使用 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 default: return [] } @@ -875,15 +898,22 @@ export function CompetitionManagement() { let searchTerm = participantSearchTerm let departmentFilterValue = departmentFilter + console.log('🔍 getFilteredParticipants 調用:', { + competitionType, + participantsLength: participants.length, + searchTerm, + departmentFilterValue + }) + // Use separate search terms for mixed competitions if (newCompetition.type === "mixed") { searchTerm = competitionType === "individual" ? individualParticipantSearchTerm : teamParticipantSearchTerm departmentFilterValue = competitionType === "individual" ? individualDepartmentFilter : teamDepartmentFilter } - return participants.filter((participant) => { + const filtered = participants.filter((participant) => { const searchField = competitionType === "team" ? participant.name : participant.name - const creatorField = competitionType === "team" ? participant.leader : participant.creator + const creatorField = competitionType === "team" ? (participant.leader_name || participant.leader) : (participant.creator_name || participant.creator_id || participant.creator) const matchesSearch = searchField.toLowerCase().includes(searchTerm.toLowerCase()) || @@ -891,6 +921,21 @@ export function CompetitionManagement() { const matchesDepartment = departmentFilterValue === "all" || participant.department === departmentFilterValue return matchesSearch && matchesDepartment }) + + console.log('🔍 getFilteredParticipants 結果:', { + competitionType, + filteredLength: filtered.length, + filtered: filtered.slice(0, 2).map(f => ({ + id: f.id, + name: f.name, + leader_name: f.leader_name, + leader: f.leader, + member_count: f.member_count, + submissionDate: f.submissionDate + })) + }) + + return filtered } const resetForm = () => { @@ -2522,7 +2567,7 @@ export function CompetitionManagement() {
- {isCurrentCompetition && } + {isCurrentCompetition && }

{competition.name}

{competition.description}

@@ -3092,6 +3137,7 @@ export function CompetitionManagement() { const matchesExpertise = judgeExpertiseFilter === "all" || judge.expertise.some(exp => exp.includes(judgeExpertiseFilter)) + const matchesStatus = judgeStatusFilter === "all" || (judgeStatusFilter === "active" && judge.is_active === true) || (judgeStatusFilter === "inactive" && judge.is_active === false) @@ -3124,7 +3170,7 @@ export function CompetitionManagement() {
- + {judge.name[0]} @@ -3974,7 +4020,7 @@ export function CompetitionManagement() { }} /> - + {judge.name[0]} @@ -4386,7 +4432,7 @@ export function CompetitionManagement() { }} /> - + {judge.name[0]} @@ -4789,7 +4835,7 @@ export function CompetitionManagement() { }} /> - + {judge.name[0]} @@ -5205,8 +5251,8 @@ export function CompetitionManagement() { {participant.department}
-

創作者:{participant.creator}

-

提交日期:{participant.submissionDate || participant.created_at || '未知'}

+

創作者:{participant.creator_name || participant.creator_id || participant.creator || '未知'}

+

提交日期:{participant.submissionDate ? new Date(participant.submissionDate).toLocaleDateString('zh-TW', { timeZone: 'Asia/Taipei', year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/-/g, '/') : '未知'}

) @@ -5263,8 +5309,32 @@ export function CompetitionManagement() {
- {getFilteredParticipants("team").map((participant) => { + {isLoadingTeams ? ( +
+
+
+

載入團隊數據中...

+
+
+ ) : ( + getFilteredParticipants("team").map((participant) => { 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 (
{participant.description}

)} -

提交日期:{participant.submissionDate || participant.created_at || '未知'}

+

提交日期:{participant.submissionDate ? new Date(participant.submissionDate).toLocaleDateString('zh-TW', { timeZone: 'Asia/Taipei', year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/-/g, '/') : '未知'}

) - })} + }) + )}
@@ -5432,11 +5503,11 @@ export function CompetitionManagement() {
- 隊長:{participant.leader} + 隊長:{participant.leader_name || participant.leader || '未知'}
- {participant.memberCount}人 + {participant.member_count || participant.memberCount || 0}人
@@ -5497,10 +5568,16 @@ export function CompetitionManagement() { {/* Individual information */} {newCompetition.type === "individual" && ( -

創作者:{participant.creator}

+
+

創作者:{participant.creator_name || participant.creator_id || participant.creator || '未知'}

+

類型:{participant.type || participant.category || '未知'}

+ {participant.description && ( +

{participant.description}

+ )} +
)} -

提交日期:{participant.submissionDate}

+

提交日期:{participant.submissionDate ? new Date(participant.submissionDate).toLocaleDateString('zh-TW', { timeZone: 'Asia/Taipei', year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/-/g, '/') : '未知'}

) diff --git a/components/admin/team-management.tsx b/components/admin/team-management.tsx index 71a2330..8cefac8 100644 --- a/components/admin/team-management.tsx +++ b/components/admin/team-management.tsx @@ -60,10 +60,15 @@ export function TeamManagement() { const [newMember, setNewMember] = useState({ name: "", + user_id: "", department: "HQBU", role: "成員", }) + // 可用用戶狀態 + const [availableUsers, setAvailableUsers] = useState([]) + const [isLoadingUsers, setIsLoadingUsers] = useState(false) + // 獲取團隊數據 const fetchTeams = async () => { try { @@ -87,8 +92,30 @@ export function TeamManagement() { useEffect(() => { fetchTeams() + fetchAvailableUsers() }, []) + // 獲取可用用戶列表 + const fetchAvailableUsers = async () => { + try { + setIsLoadingUsers(true) + const response = await fetch('/api/admin/users/available') + const data = await response.json() + + if (data.success) { + setAvailableUsers(data.data) + } else { + console.error('獲取用戶列表失敗:', data.message) + setError('獲取用戶列表失敗') + } + } catch (error) { + console.error('獲取用戶列表失敗:', error) + setError('獲取用戶列表失敗') + } finally { + setIsLoadingUsers(false) + } + } + const filteredTeams = apiTeams.filter((team) => { const matchesSearch = team.name.toLowerCase().includes(searchTerm.toLowerCase()) || @@ -102,31 +129,46 @@ export function TeamManagement() { setShowTeamDetail(true) } - const handleEditTeam = (team: any) => { + const handleEditTeam = async (team: any) => { setSelectedTeam(team) - // 確保成員數據結構正確 - const members = team.members && Array.isArray(team.members) - ? team.members.map((member: any) => ({ - id: member.user_id || member.id, - user_id: member.user_id || member.id, - name: member.name, - department: member.department, - role: member.role || '成員' - })) - : [] - - setNewTeam({ - name: team.name, - department: team.department, - contactEmail: team.contact_email || team.contactEmail, - description: team.description || '', - members: members, - leader: team.leader_id || team.leader, - apps: team.apps || [], - totalLikes: team.total_likes || team.totalLikes || 0, - }) - setShowEditTeam(true) + try { + // 獲取團隊的詳細信息,包括成員 + const response = await fetch(`/api/admin/teams/${team.id}`) + const data = await response.json() + + if (data.success) { + const teamDetails = data.data + + // 確保成員數據結構正確 + const members = teamDetails.members && Array.isArray(teamDetails.members) + ? teamDetails.members.map((member: any) => ({ + id: member.user_id || member.id, + user_id: member.user_id || member.id, + name: member.name, + department: member.department, + role: member.role || '成員' + })) + : [] + + setNewTeam({ + name: teamDetails.name, + department: teamDetails.department, + contactEmail: teamDetails.contact_email || teamDetails.contactEmail, + description: teamDetails.description || '', + members: members, + leader: teamDetails.leader_id || teamDetails.leader, + apps: teamDetails.apps || [], + totalLikes: teamDetails.total_likes || teamDetails.totalLikes || 0, + }) + setShowEditTeam(true) + } else { + setError('獲取團隊詳情失敗') + } + } catch (error) { + console.error('獲取團隊詳情失敗:', error) + setError('獲取團隊詳情失敗') + } } const handleDeleteTeam = (team: Team) => { @@ -145,13 +187,20 @@ export function TeamManagement() { } const handleAddMember = () => { - if (!newMember.name.trim()) { - setError("請輸入成員姓名") + if (!newMember.user_id || !newMember.name.trim()) { + setError("請選擇成員") + return + } + + // 檢查是否已經添加過這個成員 + if (newTeam.members.some(member => member.user_id === newMember.user_id)) { + setError("該成員已經在團隊中") return } const member: TeamMember = { - id: `m${Date.now()}`, + id: newMember.user_id, // 使用真實的用戶 ID + user_id: newMember.user_id, name: newMember.name, department: newMember.department, role: newMember.role, @@ -173,6 +222,7 @@ export function TeamManagement() { setNewMember({ name: "", + user_id: "", department: "HQBU", role: "成員", }) @@ -262,6 +312,9 @@ export function TeamManagement() { })) } + console.log('🔍 準備更新的團隊數據:', updateData) + console.log('🔍 成員數據:', newTeam.members) + // 調用 API 更新團隊 const response = await fetch(`/api/admin/teams/${selectedTeam.id}`, { method: 'PUT', @@ -731,13 +784,30 @@ export function TeamManagement() {

新增成員

- - setNewMember({ ...newMember, name: e.target.value })} - placeholder="輸入成員姓名" - /> + +
diff --git a/components/auth/profile-dialog.tsx b/components/auth/profile-dialog.tsx index 63cdee4..3fd0107 100644 --- a/components/auth/profile-dialog.tsx +++ b/components/auth/profile-dialog.tsx @@ -79,7 +79,7 @@ export function ProfileDialog({ open, onOpenChange }: ProfileDialogProps) {
- + {user?.name?.charAt(0) || "U"} diff --git a/components/competition/award-detail-dialog.tsx b/components/competition/award-detail-dialog.tsx index 29dfb87..d826a2d 100644 --- a/components/competition/award-detail-dialog.tsx +++ b/components/competition/award-detail-dialog.tsx @@ -427,7 +427,7 @@ export function AwardDetailDialog({ open, onOpenChange, award }: AwardDetailDial return (
- + {judge.name[0]}
@@ -495,7 +495,7 @@ export function AwardDetailDialog({ open, onOpenChange, award }: AwardDetailDial
- + {judgeScore.judgeName[0]}
diff --git a/components/competition/competition-detail-dialog.tsx b/components/competition/competition-detail-dialog.tsx index 245463b..36e1414 100644 --- a/components/competition/competition-detail-dialog.tsx +++ b/components/competition/competition-detail-dialog.tsx @@ -192,7 +192,7 @@ export function CompetitionDetailDialog({
- + {judge.name[0]} @@ -377,7 +377,7 @@ export function CompetitionDetailDialog({ >
- + {member.name[0]}
@@ -586,7 +586,7 @@ export function CompetitionDetailDialog({
- + {judge.name[0]} @@ -677,7 +677,7 @@ export function CompetitionDetailDialog({ >
- + {member.name[0]}
diff --git a/components/competition/judge-scoring-dialog.tsx b/components/competition/judge-scoring-dialog.tsx index 98827b1..98c79ca 100644 --- a/components/competition/judge-scoring-dialog.tsx +++ b/components/competition/judge-scoring-dialog.tsx @@ -145,7 +145,7 @@ export function JudgeScoringDialog({ open, onOpenChange, appId, appName, judgeId
- + {judge.name[0]}
diff --git a/components/competition/popularity-rankings.tsx b/components/competition/popularity-rankings.tsx index dc38023..5154bcb 100644 --- a/components/competition/popularity-rankings.tsx +++ b/components/competition/popularity-rankings.tsx @@ -693,7 +693,7 @@ export function PopularityRankings() { {competitionJudges.map((judge) => (
- + {judge.name[0]}
diff --git a/components/competition/team-detail-dialog.tsx b/components/competition/team-detail-dialog.tsx index 7527a72..f88384a 100644 --- a/components/competition/team-detail-dialog.tsx +++ b/components/competition/team-detail-dialog.tsx @@ -309,7 +309,7 @@ export function TeamDetailDialog({ open, onOpenChange, team }: TeamDetailDialogP {team.members.map((member: any, index: number) => (
- + {member.name[0]} diff --git a/lib/services/database-service.ts b/lib/services/database-service.ts index a6a5694..f0d6246 100644 --- a/lib/services/database-service.ts +++ b/lib/services/database-service.ts @@ -882,6 +882,7 @@ export class JudgeService extends DatabaseServiceBase { const result = await db.queryOne(sql, [id]); if (result) { result.expertise = this.parseExpertise(result.expertise as any); + result.is_active = Boolean(result.is_active); // 確保 is_active 是布爾值 } return result; } @@ -892,7 +893,8 @@ export class JudgeService extends DatabaseServiceBase { const results = await db.query(sql); return results.map(judge => ({ ...judge, - expertise: this.parseExpertise(judge.expertise as any) + expertise: this.parseExpertise(judge.expertise as any), + is_active: Boolean(judge.is_active) // 確保 is_active 是布爾值 })); } @@ -1010,17 +1012,26 @@ export class TeamService extends DatabaseServiceBase { SELECT t.*, u.name as leader_name, u.phone as leader_phone, + t.leader_id as leader, COUNT(DISTINCT tm.id) as member_count, - COUNT(DISTINCT a.id) as app_count + COUNT(DISTINCT a.id) as app_count, + t.created_at as submissionDate FROM teams t LEFT JOIN users u ON t.leader_id = u.id LEFT JOIN team_members tm ON t.id = tm.team_id LEFT JOIN apps a ON t.id = a.team_id AND a.is_active = 1 WHERE t.is_active = 1 - GROUP BY t.id + GROUP BY t.id, t.name, t.leader_id, t.department, t.contact_email, t.total_likes, t.is_active, t.created_at, t.updated_at, u.name, u.phone ORDER BY t.created_at DESC `; 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; } @@ -1104,9 +1115,11 @@ export class TeamService extends DatabaseServiceBase { for (const member of updates.members) { if (member.user_id && member.role) { console.log(`添加成員: ${member.user_id} (${member.role})`); + // 生成唯一的 ID + const memberId = `tm_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; await DatabaseServiceBase.safeInsert( - 'INSERT INTO team_members (team_id, user_id, role, joined_at) VALUES (?, ?, ?, NOW())', - [id, member.user_id, member.role] + 'INSERT INTO team_members (id, team_id, user_id, role, joined_at) VALUES (?, ?, ?, ?, NOW())', + [memberId, id, member.user_id, member.role] ); } } diff --git a/scripts/insert-team-members.sql b/scripts/insert-team-members.sql new file mode 100644 index 0000000..5fdf5ad --- /dev/null +++ b/scripts/insert-team-members.sql @@ -0,0 +1,50 @@ +-- ===================================================== +-- 插入團隊成員測試數據 +-- ===================================================== + +-- 首先查看現有的團隊 +SELECT '=== 現有團隊 ===' as info; +SELECT id, name, leader_id FROM teams WHERE is_active = 1; + +-- 查看現有用戶 +SELECT '=== 現有用戶 ===' as info; +SELECT id, name, department FROM users WHERE status = 'active' LIMIT 10; + +-- 為每個團隊插入成員數據 +-- 團隊 1: aaa (ID: t1757702332911zcl6iafq1) +INSERT IGNORE INTO team_members (id, team_id, user_id, role, joined_at) VALUES +('tm_1_1', 't1757702332911zcl6iafq1', 'db19b491-8f63-44b5-a28a-1f8eeb4fdd3c', '隊長', NOW()), +('tm_1_2', 't1757702332911zcl6iafq1', 'db19b491-8f63-44b5-a28a-1f8eeb4fdd3c', '成員', NOW()); + +-- 如果有其他團隊,請按照相同格式添加 +-- 團隊 2: (如果有的話) +-- INSERT IGNORE INTO team_members (id, team_id, user_id, role, joined_at) VALUES +-- ('tm_2_1', 'team_id_2', 'user_id_2', '隊長', NOW()), +-- ('tm_2_2', 'team_id_2', 'user_id_3', '成員', NOW()); + +-- 驗證插入結果 +SELECT '=== 團隊成員插入結果 ===' as info; +SELECT + tm.id, + tm.team_id, + t.name as team_name, + tm.user_id, + u.name as user_name, + tm.role, + tm.joined_at +FROM team_members tm +LEFT JOIN teams t ON tm.team_id = t.id +LEFT JOIN users u ON tm.user_id = u.id +ORDER BY tm.team_id, tm.role; + +-- 檢查團隊成員統計 +SELECT '=== 團隊成員統計 ===' as info; +SELECT + t.id, + t.name as team_name, + COUNT(tm.id) as member_count +FROM teams t +LEFT JOIN team_members tm ON t.id = tm.team_id +WHERE t.is_active = 1 +GROUP BY t.id, t.name +ORDER BY t.name; diff --git a/scripts/insert-test-team-members.js b/scripts/insert-test-team-members.js new file mode 100644 index 0000000..97097a7 --- /dev/null +++ b/scripts/insert-test-team-members.js @@ -0,0 +1,82 @@ +const mysql = require('mysql2/promise'); + +async function insertTeamMembers() { + const connection = await mysql.createConnection({ + host: 'mysql.theaken.com', + port: 33306, + user: 'AI_Platform', + password: 'Aa123456', + database: 'db_AI_Platform', + charset: 'utf8mb4', + timezone: '+08:00' + }); + + try { + console.log('🔍 開始插入團隊成員測試數據...'); + + // 查看現有團隊 + const [teams] = await connection.execute('SELECT id, name, leader_id FROM teams WHERE is_active = 1 LIMIT 5'); + console.log('現有團隊:', teams); + + // 查看現有用戶 + const [users] = await connection.execute('SELECT id, name FROM users WHERE status = "active" LIMIT 5'); + console.log('現有用戶:', users); + + if (teams.length > 0 && users.length > 0) { + // 為每個團隊插入成員 + for (let i = 0; i < Math.min(teams.length, 3); i++) { + const team = teams[i]; + const teamId = team.id; + + // 插入隊長(使用團隊的 leader_id) + await connection.execute( + 'INSERT IGNORE INTO team_members (id, team_id, user_id, role, joined_at) VALUES (?, ?, ?, ?, NOW())', + [`tm_${Date.now()}_${i}_1`, teamId, team.leader_id, '隊長'] + ); + + // 插入成員(使用其他用戶) + for (let j = 1; j < Math.min(users.length, 3); j++) { + if (users[j].id !== team.leader_id) { + await connection.execute( + 'INSERT IGNORE INTO team_members (id, team_id, user_id, role, joined_at) VALUES (?, ?, ?, ?, NOW())', + [`tm_${Date.now()}_${i}_${j+1}`, teamId, users[j].id, '成員'] + ); + } + } + + console.log(`✅ 團隊 ${team.name} 成員插入成功`); + } + } + + // 驗證結果 + const [members] = await connection.execute(` + SELECT tm.*, t.name as team_name, u.name as user_name + FROM team_members tm + LEFT JOIN teams t ON tm.team_id = t.id + LEFT JOIN users u ON tm.user_id = u.id + ORDER BY tm.team_id + `); + console.log('📊 團隊成員統計:', members); + + // 檢查團隊成員數量 + const [counts] = await connection.execute(` + SELECT + t.id, + t.name as team_name, + COUNT(tm.id) as member_count + FROM teams t + LEFT JOIN team_members tm ON t.id = tm.team_id + WHERE t.is_active = 1 + GROUP BY t.id, t.name + ORDER BY t.name + `); + console.log('📈 團隊成員數量:', counts); + + } catch (error) { + console.error('❌ 錯誤:', error); + } finally { + await connection.end(); + } +} + +insertTeamMembers(); diff --git a/scripts/test-team-data.js b/scripts/test-team-data.js new file mode 100644 index 0000000..cb2bc85 --- /dev/null +++ b/scripts/test-team-data.js @@ -0,0 +1,70 @@ +const mysql = require('mysql2/promise'); + +async function testTeamData() { + const connection = await mysql.createConnection({ + host: 'mysql.theaken.com', + port: 33306, + user: 'AI_Platform', + password: 'Aa123456', + database: 'db_AI_Platform', + charset: 'utf8mb4', + timezone: '+08:00' + }); + + try { + console.log('🔍 測試團隊數據查詢...'); + + // 使用與 getAllTeams 相同的查詢 + const [results] = await connection.execute(` + SELECT t.*, + u.name as leader_name, + u.phone as leader_phone, + t.leader_id as leader, + COUNT(DISTINCT tm.id) as member_count, + COUNT(DISTINCT a.id) as app_count, + t.created_at as submissionDate + FROM teams t + LEFT JOIN users u ON t.leader_id = u.id + LEFT JOIN team_members tm ON t.id = tm.team_id + LEFT JOIN apps a ON t.id = a.team_id AND a.is_active = 1 + WHERE t.is_active = 1 + GROUP BY t.id, t.name, t.leader_id, t.department, t.contact_email, t.total_likes, t.is_active, t.created_at, t.updated_at, u.name, u.phone + ORDER BY t.created_at DESC + LIMIT 5 + `); + + console.log('📊 查詢結果:'); + results.forEach((team, index) => { + console.log(`\n團隊 ${index + 1}:`); + console.log(` 名稱: ${team.name}`); + console.log(` 隊長ID: ${team.leader}`); + console.log(` 隊長姓名: ${team.leader_name || 'NULL'}`); + console.log(` 成員數量: ${team.member_count || 0}`); + console.log(` 提交日期: ${team.submissionDate || 'NULL'}`); + console.log(` 部門: ${team.department}`); + }); + + // 檢查特定團隊的成員 + if (results.length > 0) { + const teamId = results[0].id; + const [members] = await connection.execute(` + SELECT tm.*, u.name as user_name + FROM team_members tm + LEFT JOIN users u ON tm.user_id = u.id + WHERE tm.team_id = ? + `, [teamId]); + + console.log(`\n🔍 團隊 ${results[0].name} 的成員:`); + members.forEach(member => { + console.log(` - ${member.user_name} (${member.role})`); + }); + } + + } catch (error) { + console.error('❌ 錯誤:', error); + } finally { + await connection.end(); + } +} + +testTeamData(); diff --git a/types/competition.ts b/types/competition.ts index 8648c5a..925307b 100644 --- a/types/competition.ts +++ b/types/competition.ts @@ -24,6 +24,7 @@ export interface JudgeScore { // 新增團隊成員接口 export interface TeamMember { id: string + user_id?: string // 添加可選的 user_id 字段 name: string department: string role: string // 角色:隊長、成員等