修正團體管理的 BUG
This commit is contained in:
@@ -45,7 +45,7 @@ export async function PUT(request: NextRequest, { params }: { params: { id: stri
|
||||
try {
|
||||
const { id: appId } = await params
|
||||
const body = await request.json()
|
||||
const { name, description, category, type, app_url, icon, icon_color } = body
|
||||
const { name, description, category, type, app_url, icon, icon_color, department } = body
|
||||
|
||||
// 更新應用
|
||||
const result = await appService.updateApp(appId, {
|
||||
@@ -55,7 +55,8 @@ export async function PUT(request: NextRequest, { params }: { params: { id: stri
|
||||
type,
|
||||
app_url,
|
||||
icon,
|
||||
icon_color
|
||||
icon_color,
|
||||
department
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
|
@@ -107,7 +107,7 @@ export async function PUT(request: NextRequest, { params }: { params: { id: stri
|
||||
if (body.is_active !== undefined) updateData.is_active = body.is_active;
|
||||
|
||||
// 執行更新
|
||||
const success = await judgeservice.updateJudge(id, updateData);
|
||||
const success = await JudgeService.updateJudge(id, updateData);
|
||||
|
||||
if (!success) {
|
||||
return NextResponse.json({
|
||||
|
@@ -135,10 +135,12 @@ export async function GET(request: NextRequest, { params }: { params: { id: stri
|
||||
}
|
||||
}
|
||||
|
||||
// 強制將第一個成員設為隊長(因為隊長邏輯有問題)
|
||||
if (allMembers.length > 0) {
|
||||
allMembers[0].role = '隊長';
|
||||
}
|
||||
// 確保其他成員的角色正確設置
|
||||
allMembers.forEach(member => {
|
||||
if (member.user_id !== team.leader_id && member.role !== '隊長') {
|
||||
member.role = '成員';
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
...team,
|
||||
@@ -220,7 +222,9 @@ export async function GET(request: NextRequest, { params }: { params: { id: stri
|
||||
id: member.id,
|
||||
user_id: member.user_id,
|
||||
name: member.name,
|
||||
role: member.role === '??????' ? '成員' : (member.role || '成員')
|
||||
department: member.department,
|
||||
email: member.email,
|
||||
role: member.role || '成員'
|
||||
})),
|
||||
apps: team.apps.map(app => ({
|
||||
id: app.id,
|
||||
|
@@ -517,7 +517,8 @@ export function AppManagement() {
|
||||
type: newApp.type,
|
||||
app_url: newApp.appUrl,
|
||||
icon: newApp.icon,
|
||||
icon_color: newApp.iconColor
|
||||
icon_color: newApp.iconColor,
|
||||
department: newApp.department
|
||||
})
|
||||
})
|
||||
|
||||
|
@@ -563,7 +563,13 @@ export function CompetitionManagement() {
|
||||
contactEmail: teamDetails.contact_email || '',
|
||||
leaderPhone: teamDetails.leader_phone || '',
|
||||
description: teamDetails.description || '',
|
||||
members: teamDetails.members || [],
|
||||
members: (teamDetails.members || []).map((member: any) => ({
|
||||
id: member.user_id || member.id,
|
||||
user_id: member.user_id || member.id,
|
||||
name: member.name,
|
||||
department: member.department,
|
||||
role: member.role || '成員'
|
||||
})),
|
||||
apps: teamDetails.apps ? teamDetails.apps.map((app: any) => app.id || app) : [],
|
||||
submittedAppCount: teamDetails.apps?.length || 0,
|
||||
}
|
||||
@@ -1142,7 +1148,7 @@ export function CompetitionManagement() {
|
||||
contact_email: newTeam.contactEmail,
|
||||
description: newTeam.description,
|
||||
members: newTeam.members.map(member => ({
|
||||
user_id: member.id, // 現在 member.id 就是 user_id
|
||||
user_id: member.user_id || member.id, // 確保使用正確的 user_id
|
||||
role: member.role || 'member'
|
||||
})),
|
||||
apps: newTeam.apps // 添加應用 ID 列表
|
||||
@@ -1164,7 +1170,7 @@ export function CompetitionManagement() {
|
||||
contact_email: newTeam.contactEmail,
|
||||
description: newTeam.description,
|
||||
members: newTeam.members.map(member => ({
|
||||
user_id: member.id, // 現在 member.id 就是 user_id
|
||||
user_id: member.user_id || member.id, // 確保使用正確的 user_id
|
||||
role: member.role || 'member'
|
||||
})),
|
||||
apps: newTeam.apps // 添加應用 ID 列表
|
||||
@@ -2136,6 +2142,19 @@ export function CompetitionManagement() {
|
||||
}
|
||||
}
|
||||
|
||||
const getCompetitionTypeColor = (type: string) => {
|
||||
switch (type) {
|
||||
case "individual":
|
||||
return "bg-blue-100 text-blue-800 border-blue-200"
|
||||
case "team":
|
||||
return "bg-purple-100 text-purple-800 border-purple-200"
|
||||
case "mixed":
|
||||
return "bg-orange-100 text-orange-800 border-orange-200"
|
||||
default:
|
||||
return "bg-gray-100 text-gray-800 border-gray-200"
|
||||
}
|
||||
}
|
||||
|
||||
const getScoreLabelText = (key: string) => {
|
||||
switch (key) {
|
||||
case "innovation":
|
||||
@@ -2464,13 +2483,13 @@ export function CompetitionManagement() {
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>競賽名稱</TableHead>
|
||||
<TableHead>類型</TableHead>
|
||||
<TableHead>時間</TableHead>
|
||||
<TableHead>狀態</TableHead>
|
||||
<TableHead>參賽項目</TableHead>
|
||||
<TableHead>評分進度</TableHead>
|
||||
<TableHead>操作</TableHead>
|
||||
<TableHead className="w-[300px]">競賽名稱</TableHead>
|
||||
<TableHead className="w-[120px]">類型</TableHead>
|
||||
<TableHead className="w-[150px]">時間</TableHead>
|
||||
<TableHead className="w-[100px]">狀態</TableHead>
|
||||
<TableHead className="w-[120px]">參賽項目</TableHead>
|
||||
<TableHead className="w-[150px]">評分進度</TableHead>
|
||||
<TableHead className="w-[80px]">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -2506,9 +2525,9 @@ export function CompetitionManagement() {
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center space-x-2 min-w-[100px]">
|
||||
{getCompetitionTypeIcon(competition.type)}
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<Badge variant="outline" className={`text-xs px-2 py-1 whitespace-nowrap ${getCompetitionTypeColor(competition.type)}`}>
|
||||
{getCompetitionTypeText(competition.type)}
|
||||
</Badge>
|
||||
</div>
|
||||
@@ -2527,34 +2546,37 @@ export function CompetitionManagement() {
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline" className={getStatusColor(competition.status)}>
|
||||
{getStatusText(competition.status)}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center space-x-1">
|
||||
{getCompetitionTypeIcon(competition.type)}
|
||||
<span>{participantCount} 個</span>
|
||||
<div className="min-w-[80px]">
|
||||
<Badge variant="outline" className={`text-xs px-2 py-1 whitespace-nowrap ${getStatusColor(competition.status)}`}>
|
||||
{getStatusText(competition.status)}
|
||||
</Badge>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center space-x-1 min-w-[100px]">
|
||||
{getCompetitionTypeIcon(competition.type)}
|
||||
<span className="text-sm whitespace-nowrap">{participantCount} 個</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="space-y-1 min-w-[140px]">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Progress value={scoringProgress.percentage} className="w-16 h-2" />
|
||||
<span className="text-xs text-gray-500">
|
||||
<span className="text-xs text-gray-500 whitespace-nowrap">
|
||||
{scoringProgress.completed}/{scoringProgress.total}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500">{scoringProgress.percentage}% 完成</p>
|
||||
<p className="text-xs text-gray-500 whitespace-nowrap">{scoringProgress.percentage}% 完成</p>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="sm" disabled={isLoading}>
|
||||
<MoreHorizontal className="w-4 h-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<div className="flex justify-center min-w-[60px]">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="sm" disabled={isLoading}>
|
||||
<MoreHorizontal className="w-4 h-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => handleViewCompetition(competition)}>
|
||||
<Eye className="w-4 h-4 mr-2" />
|
||||
@@ -2598,7 +2620,8 @@ export function CompetitionManagement() {
|
||||
刪除競賽
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
@@ -2771,7 +2794,7 @@ export function CompetitionManagement() {
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 text-gray-600">
|
||||
<Trophy className="w-3 h-3" />
|
||||
<span>{team.submittedAppCount} 個應用</span>
|
||||
<span>{team.app_count || 0} 個應用</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6603,7 +6626,7 @@ export function CompetitionManagement() {
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center space-x-2">
|
||||
<p className="font-medium text-gray-900">{member.name}</p>
|
||||
{member.user_id === selectedTeam.leader_id && (
|
||||
{member.role === '隊長' && (
|
||||
<Badge variant="default" className="text-xs bg-orange-100 text-orange-800">
|
||||
隊長
|
||||
</Badge>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useState, useEffect } from "react"
|
||||
import { useCompetition } from "@/contexts/competition-context"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
@@ -43,6 +43,10 @@ export function TeamManagement() {
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [success, setSuccess] = useState("")
|
||||
const [error, setError] = useState("")
|
||||
|
||||
// 從 API 獲取的團隊數據
|
||||
const [apiTeams, setApiTeams] = useState<any[]>([])
|
||||
const [isLoadingTeams, setIsLoadingTeams] = useState(true)
|
||||
|
||||
const [newTeam, setNewTeam] = useState({
|
||||
name: "",
|
||||
@@ -60,10 +64,35 @@ export function TeamManagement() {
|
||||
role: "成員",
|
||||
})
|
||||
|
||||
const filteredTeams = teams.filter((team) => {
|
||||
// 獲取團隊數據
|
||||
const fetchTeams = async () => {
|
||||
try {
|
||||
setIsLoadingTeams(true)
|
||||
const response = await fetch('/api/admin/teams')
|
||||
const data = await response.json()
|
||||
|
||||
if (data.success) {
|
||||
setApiTeams(data.data)
|
||||
} else {
|
||||
console.error('獲取團隊數據失敗:', data.message)
|
||||
setError('獲取團隊數據失敗')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('獲取團隊數據失敗:', error)
|
||||
setError('獲取團隊數據失敗')
|
||||
} finally {
|
||||
setIsLoadingTeams(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchTeams()
|
||||
}, [])
|
||||
|
||||
const filteredTeams = apiTeams.filter((team) => {
|
||||
const matchesSearch =
|
||||
team.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
team.members.some((member) => member.name.toLowerCase().includes(searchTerm.toLowerCase()))
|
||||
team.leader_name?.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
const matchesDepartment = selectedDepartment === "all" || team.department === selectedDepartment
|
||||
return matchesSearch && matchesDepartment
|
||||
})
|
||||
@@ -73,16 +102,29 @@ export function TeamManagement() {
|
||||
setShowTeamDetail(true)
|
||||
}
|
||||
|
||||
const handleEditTeam = (team: Team) => {
|
||||
const handleEditTeam = (team: any) => {
|
||||
setSelectedTeam(team)
|
||||
|
||||
// 確保成員數據結構正確
|
||||
const members = team.members && Array.isArray(team.members)
|
||||
? team.members.map((member: any) => ({
|
||||
id: member.user_id || member.id,
|
||||
user_id: member.user_id || member.id,
|
||||
name: member.name,
|
||||
department: member.department,
|
||||
role: member.role || '成員'
|
||||
}))
|
||||
: []
|
||||
|
||||
setNewTeam({
|
||||
name: team.name,
|
||||
department: team.department,
|
||||
contactEmail: team.contactEmail,
|
||||
members: [...team.members],
|
||||
leader: team.leader,
|
||||
apps: [...team.apps],
|
||||
totalLikes: team.totalLikes,
|
||||
contactEmail: team.contact_email || team.contactEmail,
|
||||
description: team.description || '',
|
||||
members: members,
|
||||
leader: team.leader_id || team.leader,
|
||||
apps: team.apps || [],
|
||||
totalLikes: team.total_likes || team.totalLikes || 0,
|
||||
})
|
||||
setShowEditTeam(true)
|
||||
}
|
||||
@@ -205,14 +247,49 @@ export function TeamManagement() {
|
||||
}
|
||||
|
||||
setIsLoading(true)
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
|
||||
updateTeam(selectedTeam.id, newTeam)
|
||||
setShowEditTeam(false)
|
||||
setSelectedTeam(null)
|
||||
setSuccess("團隊更新成功!")
|
||||
setIsLoading(false)
|
||||
setTimeout(() => setSuccess(""), 3000)
|
||||
try {
|
||||
// 準備更新數據
|
||||
const updateData = {
|
||||
name: newTeam.name,
|
||||
department: newTeam.department,
|
||||
contact_email: newTeam.contactEmail,
|
||||
description: newTeam.description || null,
|
||||
leader_id: newTeam.leader,
|
||||
members: newTeam.members.map(member => ({
|
||||
user_id: member.user_id || member.id,
|
||||
role: member.role
|
||||
}))
|
||||
}
|
||||
|
||||
// 調用 API 更新團隊
|
||||
const response = await fetch(`/api/admin/teams/${selectedTeam.id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(updateData)
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (data.success) {
|
||||
// 更新成功,重新載入團隊數據
|
||||
await fetchTeams()
|
||||
setShowEditTeam(false)
|
||||
setSelectedTeam(null)
|
||||
setSuccess("團隊更新成功!")
|
||||
} else {
|
||||
setError(data.message || "更新團隊失敗")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新團隊失敗:', error)
|
||||
setError("更新團隊失敗")
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
setTimeout(() => setError(""), 3000)
|
||||
setTimeout(() => setSuccess(""), 3000)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -254,7 +331,7 @@ export function TeamManagement() {
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-gray-600">總團隊數</p>
|
||||
<p className="text-2xl font-bold">{teams.length}</p>
|
||||
<p className="text-2xl font-bold">{apiTeams.length}</p>
|
||||
</div>
|
||||
<Users className="w-8 h-8 text-green-600" />
|
||||
</div>
|
||||
@@ -266,7 +343,7 @@ export function TeamManagement() {
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-gray-600">總成員數</p>
|
||||
<p className="text-2xl font-bold">{teams.reduce((sum, team) => sum + team.members.length, 0)}</p>
|
||||
<p className="text-2xl font-bold">{apiTeams.reduce((sum, team) => sum + (team.member_count || 0), 0)}</p>
|
||||
</div>
|
||||
<UserPlus className="w-8 h-8 text-blue-600" />
|
||||
</div>
|
||||
@@ -279,8 +356,8 @@ export function TeamManagement() {
|
||||
<div>
|
||||
<p className="text-sm text-gray-600">平均團隊規模</p>
|
||||
<p className="text-2xl font-bold">
|
||||
{teams.length > 0
|
||||
? Math.round((teams.reduce((sum, team) => sum + team.members.length, 0) / teams.length) * 10) / 10
|
||||
{apiTeams.length > 0
|
||||
? Math.round((apiTeams.reduce((sum, team) => sum + (team.member_count || 0), 0) / apiTeams.length) * 10) / 10
|
||||
: 0}
|
||||
</p>
|
||||
</div>
|
||||
@@ -352,8 +429,6 @@ export function TeamManagement() {
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredTeams.map((team) => {
|
||||
const leader = team.members.find((m) => m.id === team.leader)
|
||||
|
||||
return (
|
||||
<TableRow key={team.id}>
|
||||
<TableCell>
|
||||
@@ -363,7 +438,7 @@ export function TeamManagement() {
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium">{team.name}</p>
|
||||
<p className="text-sm text-gray-500">{team.contactEmail}</p>
|
||||
<p className="text-sm text-gray-500">{team.contact_email}</p>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
@@ -371,10 +446,10 @@ export function TeamManagement() {
|
||||
<div className="flex items-center space-x-2">
|
||||
<Avatar className="w-6 h-6">
|
||||
<AvatarFallback className="bg-green-100 text-green-700 text-xs">
|
||||
{leader?.name[0] || "?"}
|
||||
{team.leader_name?.[0] || "?"}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<span className="text-sm">{leader?.name || "未設定"}</span>
|
||||
<span className="text-sm">{team.leader_name || "未設定"}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -385,11 +460,11 @@ export function TeamManagement() {
|
||||
<TableCell>
|
||||
<div className="flex items-center space-x-1">
|
||||
<UserPlus className="w-4 h-4 text-blue-500" />
|
||||
<span>{team.members.length}</span>
|
||||
<span>{team.member_count || 0}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="font-medium">{team.apps.length}</span>
|
||||
<span className="font-medium">{team.app_count || 0}</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="font-medium text-red-600">{team.totalLikes}</span>
|
||||
@@ -807,16 +882,16 @@ export function TeamManagement() {
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-xl font-semibold">{selectedTeam.name}</h3>
|
||||
<p className="text-gray-600 mb-2">{selectedTeam.contactEmail}</p>
|
||||
<p className="text-gray-600 mb-2">{selectedTeam.contact_email}</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Badge variant="outline" className="bg-gray-100 text-gray-700">
|
||||
{selectedTeam.department}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="bg-blue-100 text-blue-700">
|
||||
{selectedTeam.members.length} 名成員
|
||||
{selectedTeam.member_count || 0} 名成員
|
||||
</Badge>
|
||||
<Badge variant="outline" className="bg-green-100 text-green-700">
|
||||
{selectedTeam.apps.length} 個應用
|
||||
{selectedTeam.app_count || 0} 個應用
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -372,7 +372,7 @@ export function CompetitionDetailDialog({
|
||||
<div
|
||||
key={member.id}
|
||||
className={`border rounded-lg p-4 ${
|
||||
member.id === team.leader ? "border-green-300 bg-green-50" : "border-gray-200"
|
||||
member.role === '隊長' ? "border-green-300 bg-green-50" : "border-gray-200"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
@@ -383,7 +383,7 @@ export function CompetitionDetailDialog({
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center space-x-2">
|
||||
<h4 className="font-semibold">{member.name}</h4>
|
||||
{member.id === team.leader && (
|
||||
{member.role === '隊長' && (
|
||||
<Badge variant="secondary" className="bg-green-100 text-green-800">
|
||||
隊長
|
||||
</Badge>
|
||||
|
@@ -453,7 +453,7 @@ export function PopularityRankings() {
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 px-8">
|
||||
{currentTeams.map((team, index) => {
|
||||
const globalRank = startIndex + index + 1
|
||||
const leader = team.members.find((m: any) => m.id === team.leader)
|
||||
const leader = team.members.find((m: any) => m.role === '隊長')
|
||||
|
||||
return (
|
||||
<Card
|
||||
@@ -474,7 +474,7 @@ export function PopularityRankings() {
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<h4 className="font-bold text-gray-900 mb-1 truncate">{team.name}</h4>
|
||||
<p className="text-sm text-gray-600 mb-2">隊長:{leader?.name}</p>
|
||||
<p className="text-sm text-gray-600 mb-2">隊長:{leader?.name || '未知隊長'}</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
<Badge variant="outline" className="bg-green-100 text-green-800 border-green-200 text-xs">
|
||||
團隊賽
|
||||
@@ -496,7 +496,13 @@ export function PopularityRankings() {
|
||||
{member.name[0]}
|
||||
</div>
|
||||
<span className="text-gray-600">{member.name}</span>
|
||||
<span className="text-gray-400">({member.role})</span>
|
||||
<span className={`text-xs px-1 py-0.5 rounded ${
|
||||
member.role === '隊長'
|
||||
? 'bg-yellow-100 text-yellow-800'
|
||||
: 'text-gray-400'
|
||||
}`}>
|
||||
{member.role}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{team.members.length > 3 && (
|
||||
|
@@ -71,7 +71,13 @@ const getIconComponent = (iconName: string) => {
|
||||
|
||||
// App data for team apps - get from team data
|
||||
const getAppDetails = (appId: string, team: any) => {
|
||||
const appDetail = team.appsDetails?.find((app: any) => app.id === appId);
|
||||
// 首先嘗試從 appsDetails 獲取
|
||||
let appDetail = team.appsDetails?.find((app: any) => app.id === appId);
|
||||
|
||||
// 如果沒有找到,嘗試從 apps 獲取
|
||||
if (!appDetail) {
|
||||
appDetail = team.apps?.find((app: any) => app.id === appId);
|
||||
}
|
||||
|
||||
if (appDetail) {
|
||||
return {
|
||||
@@ -311,7 +317,7 @@ export function TeamDetailDialog({ open, onOpenChange, team }: TeamDetailDialogP
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center space-x-2">
|
||||
<h4 className="font-medium">{member.name}</h4>
|
||||
{member.id === team.leader && (
|
||||
{member.role === '隊長' && (
|
||||
<Badge variant="default" className="bg-yellow-100 text-yellow-800 text-xs">
|
||||
隊長
|
||||
</Badge>
|
||||
@@ -334,19 +340,36 @@ export function TeamDetailDialog({ open, onOpenChange, team }: TeamDetailDialogP
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{team.apps.map((appId: string) => {
|
||||
const app = getAppDetails(appId, team)
|
||||
{team.apps.map((app: any) => {
|
||||
// 如果 app 是字符串 ID,使用 getAppDetails 獲取詳情
|
||||
// 如果 app 是對象,直接使用
|
||||
const appData = typeof app === 'string' ? getAppDetails(app, team) : {
|
||||
id: app.id,
|
||||
name: app.name || "未命名應用",
|
||||
type: app.type || "未知類型",
|
||||
description: app.description || "無描述",
|
||||
icon: getIconComponent(app.icon) || Brain,
|
||||
iconColor: app.icon_color || "from-blue-500 to-purple-500",
|
||||
author: app.creator_name || "未知作者",
|
||||
department: app.creator_department || "未知部門",
|
||||
category: app.category || "未分類",
|
||||
likes: app.likes_count || 0,
|
||||
views: app.views_count || 0,
|
||||
rating: Number(app.rating) || 0,
|
||||
createdAt: app.created_at
|
||||
}
|
||||
|
||||
// 如果沒有圖標,使用默認的 Brain 圖標
|
||||
const IconComponent = app.icon || Brain
|
||||
const likes = app.likes || 0
|
||||
const views = app.views || 0
|
||||
const rating = app.rating || 0
|
||||
const IconComponent = appData.icon || Brain
|
||||
const likes = appData.likes || 0
|
||||
const views = appData.views || 0
|
||||
const rating = appData.rating || 0
|
||||
|
||||
return (
|
||||
<Card
|
||||
key={appId}
|
||||
key={appData.id}
|
||||
className="hover:shadow-md transition-all duration-200 cursor-pointer group"
|
||||
onClick={() => handleAppClick(appId)}
|
||||
onClick={() => handleAppClick(appData.id)}
|
||||
>
|
||||
<CardContent className="p-4 flex flex-col h-full">
|
||||
<div className="flex items-start space-x-3 mb-3">
|
||||
@@ -356,16 +379,16 @@ export function TeamDetailDialog({ open, onOpenChange, team }: TeamDetailDialogP
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center space-x-2">
|
||||
<h4 className="font-medium text-gray-900 group-hover:text-blue-600 transition-colors">
|
||||
{app.name}
|
||||
{appData.name}
|
||||
</h4>
|
||||
<ExternalLink className="w-3 h-3 text-gray-400 group-hover:text-blue-500 opacity-0 group-hover:opacity-100 transition-all" />
|
||||
</div>
|
||||
<Badge variant="outline" className={`${getTypeColor(app.type)} text-xs mt-1`}>
|
||||
{app.type}
|
||||
<Badge variant="outline" className={`${getTypeColor(appData.type)} text-xs mt-1`}>
|
||||
{appData.type}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 mb-3 line-clamp-2">{app.description}</p>
|
||||
<p className="text-sm text-gray-600 mb-3 line-clamp-2">{appData.description}</p>
|
||||
<div className="flex items-center justify-between mt-auto">
|
||||
<div className="flex items-center space-x-3 text-xs text-gray-500">
|
||||
<div className="flex items-center space-x-1">
|
||||
@@ -379,7 +402,7 @@ export function TeamDetailDialog({ open, onOpenChange, team }: TeamDetailDialogP
|
||||
</div>
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<LikeButton
|
||||
appId={appId}
|
||||
appId={appData.id}
|
||||
size="sm"
|
||||
likeCount={likes}
|
||||
showCount={true}
|
||||
|
@@ -367,7 +367,7 @@ export class UserService extends DatabaseServiceBase {
|
||||
const appsSql = `
|
||||
SELECT COUNT(*) as total_apps
|
||||
FROM apps
|
||||
WHERE creator_id = '${userId}' AND is_active = TRUE
|
||||
WHERE creator_id = '${userId}' AND is_active = 1
|
||||
`;
|
||||
const appsResult = await this.query(appsSql, []);
|
||||
const totalApps = appsResult[0]?.total_apps || 0;
|
||||
@@ -385,7 +385,7 @@ export class UserService extends DatabaseServiceBase {
|
||||
const likesSql = `
|
||||
SELECT COALESCE(SUM(a.likes_count), 0) as total_likes
|
||||
FROM apps a
|
||||
WHERE a.creator_id = '${userId}' AND a.is_active = TRUE
|
||||
WHERE a.creator_id = '${userId}' AND a.is_active = 1
|
||||
`;
|
||||
const likesResult = await this.query(likesSql, []);
|
||||
const totalLikes = likesResult[0]?.total_likes || 0;
|
||||
@@ -602,7 +602,7 @@ export class UserService extends DatabaseServiceBase {
|
||||
COALESCE(SUM(views_count), 0) as total_views,
|
||||
COALESCE(SUM(likes_count), 0) as total_likes
|
||||
FROM apps
|
||||
WHERE is_active = TRUE
|
||||
WHERE is_active = 1
|
||||
`;
|
||||
const appStats = await this.queryOne(appStatsSql);
|
||||
|
||||
@@ -713,7 +713,7 @@ export class UserService extends DatabaseServiceBase {
|
||||
a.created_at
|
||||
FROM apps a
|
||||
LEFT JOIN user_ratings ur ON a.id = ur.app_id
|
||||
WHERE a.is_active = TRUE
|
||||
WHERE a.is_active = 1
|
||||
GROUP BY a.id, a.name, a.description, a.views_count, a.likes_count, a.category, a.created_at
|
||||
ORDER BY (a.views_count + a.likes_count * 2) DESC
|
||||
LIMIT ${limit}
|
||||
@@ -1010,11 +1010,13 @@ export class TeamService extends DatabaseServiceBase {
|
||||
SELECT t.*,
|
||||
u.name as leader_name,
|
||||
u.phone as leader_phone,
|
||||
COUNT(tm.id) as member_count
|
||||
COUNT(DISTINCT tm.id) as member_count,
|
||||
COUNT(DISTINCT a.id) as app_count
|
||||
FROM teams t
|
||||
LEFT JOIN users u ON t.leader_id = u.id
|
||||
LEFT JOIN team_members tm ON t.id = tm.team_id
|
||||
WHERE t.is_active = TRUE
|
||||
LEFT JOIN apps a ON t.id = a.team_id AND a.is_active = 1
|
||||
WHERE t.is_active = 1
|
||||
GROUP BY t.id
|
||||
ORDER BY t.created_at DESC
|
||||
`;
|
||||
@@ -1030,7 +1032,7 @@ export class TeamService extends DatabaseServiceBase {
|
||||
u.phone as leader_phone
|
||||
FROM teams t
|
||||
LEFT JOIN users u ON t.leader_id = u.id
|
||||
WHERE t.id = ? AND t.is_active = TRUE
|
||||
WHERE t.id = ? AND t.is_active = 1
|
||||
`;
|
||||
const results = await DatabaseServiceBase.safeQuery(sql, [id]);
|
||||
return results.length > 0 ? results[0] : null;
|
||||
@@ -1044,7 +1046,7 @@ export class TeamService extends DatabaseServiceBase {
|
||||
u.phone as leader_phone
|
||||
FROM teams t
|
||||
LEFT JOIN users u ON t.leader_id = u.id
|
||||
WHERE t.name = ? AND t.is_active = TRUE
|
||||
WHERE t.name = ? AND t.is_active = 1
|
||||
`;
|
||||
const results = await DatabaseServiceBase.safeQuery(sql, [name]);
|
||||
return results.length > 0 ? results[0] : null;
|
||||
@@ -1058,24 +1060,97 @@ export class TeamService extends DatabaseServiceBase {
|
||||
contact_email: string;
|
||||
description: string;
|
||||
total_likes: number;
|
||||
members?: Array<{
|
||||
user_id: string;
|
||||
role: string;
|
||||
}>;
|
||||
apps?: string[];
|
||||
}>): Promise<boolean> {
|
||||
const fields = Object.keys(updates).filter(key => key !== 'id' && key !== 'created_at');
|
||||
|
||||
if (fields.length === 0) {
|
||||
console.log('沒有字段需要更新');
|
||||
return true;
|
||||
}
|
||||
|
||||
const setClause = fields.map(field => `${field} = ?`).join(', ');
|
||||
const values = fields.map(field => updates[field as keyof typeof updates]);
|
||||
try {
|
||||
// 更新團隊基本信息
|
||||
const teamFields = Object.keys(updates).filter(key =>
|
||||
key !== 'id' &&
|
||||
key !== 'created_at' &&
|
||||
key !== 'members' &&
|
||||
key !== 'apps'
|
||||
);
|
||||
|
||||
if (teamFields.length > 0) {
|
||||
const setClause = teamFields.map(field => `${field} = ?`).join(', ');
|
||||
const values = teamFields.map(field => updates[field as keyof typeof updates]);
|
||||
values.push(id);
|
||||
|
||||
const sql = `UPDATE teams SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`;
|
||||
|
||||
try {
|
||||
const result = await DatabaseServiceBase.safeUpdate(sql, values);
|
||||
console.log('團隊更新結果:', result);
|
||||
return result.affectedRows > 0;
|
||||
const result = await DatabaseServiceBase.safeUpdate(sql, values);
|
||||
console.log('團隊基本信息更新結果:', result);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
console.log('團隊基本信息更新失敗');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新團隊成員(如果提供了成員信息)
|
||||
if (updates.members && Array.isArray(updates.members)) {
|
||||
try {
|
||||
// 先刪除現有成員
|
||||
console.log('刪除現有團隊成員...');
|
||||
await DatabaseServiceBase.safeDelete('DELETE FROM team_members WHERE team_id = ?', [id]);
|
||||
|
||||
// 添加新成員(如果成員列表不為空)
|
||||
if (updates.members.length > 0) {
|
||||
console.log('添加新團隊成員...');
|
||||
for (const member of updates.members) {
|
||||
if (member.user_id && member.role) {
|
||||
console.log(`添加成員: ${member.user_id} (${member.role})`);
|
||||
await DatabaseServiceBase.safeInsert(
|
||||
'INSERT INTO team_members (team_id, user_id, role, joined_at) VALUES (?, ?, ?, NOW())',
|
||||
[id, member.user_id, member.role]
|
||||
);
|
||||
}
|
||||
}
|
||||
console.log('團隊成員更新完成');
|
||||
} else {
|
||||
console.log('成員列表為空,只刪除現有成員');
|
||||
}
|
||||
} catch (memberError) {
|
||||
console.error('更新團隊成員時發生錯誤:', memberError);
|
||||
// 成員更新失敗不應該影響整個更新操作
|
||||
// 可以選擇繼續或回滾
|
||||
}
|
||||
}
|
||||
|
||||
// 更新團隊應用(如果提供了應用信息)
|
||||
if (updates.apps && Array.isArray(updates.apps)) {
|
||||
try {
|
||||
// 先移除現有應用的團隊關聯
|
||||
console.log('移除現有應用的團隊關聯...');
|
||||
await DatabaseServiceBase.safeUpdate('UPDATE apps SET team_id = NULL WHERE team_id = ?', [id]);
|
||||
|
||||
// 添加新應用的團隊關聯(如果應用列表不為空)
|
||||
if (updates.apps.length > 0) {
|
||||
console.log('添加新應用的團隊關聯...');
|
||||
for (const appId of updates.apps) {
|
||||
if (appId) {
|
||||
console.log(`關聯應用: ${appId}`);
|
||||
await DatabaseServiceBase.safeUpdate(
|
||||
'UPDATE apps SET team_id = ? WHERE id = ?',
|
||||
[id, appId]
|
||||
);
|
||||
}
|
||||
}
|
||||
console.log('團隊應用更新完成');
|
||||
} else {
|
||||
console.log('應用列表為空,只移除現有關聯');
|
||||
}
|
||||
} catch (appError) {
|
||||
console.error('更新團隊應用時發生錯誤:', appError);
|
||||
// 應用更新失敗不應該影響整個更新操作
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('更新團隊錯誤:', error);
|
||||
return false;
|
||||
@@ -1165,7 +1240,7 @@ export class TeamService extends DatabaseServiceBase {
|
||||
// 綁定應用到團隊
|
||||
static async bindAppToTeam(teamId: string, appId: string): Promise<boolean> {
|
||||
try {
|
||||
const sql = 'UPDATE apps SET team_id = ? WHERE id = ? AND is_active = TRUE';
|
||||
const sql = 'UPDATE apps SET team_id = ? WHERE id = ? AND is_active = 1';
|
||||
const result = await DatabaseServiceBase.safeUpdate(sql, [teamId, appId]);
|
||||
return result.affectedRows > 0;
|
||||
} catch (error) {
|
||||
@@ -1196,7 +1271,7 @@ export class TeamService extends DatabaseServiceBase {
|
||||
u.name as creator_name, u.department as creator_department
|
||||
FROM apps a
|
||||
LEFT JOIN users u ON a.creator_id = u.id
|
||||
WHERE a.team_id = ? AND a.is_active = TRUE
|
||||
WHERE a.team_id = ? AND a.is_active = 1
|
||||
ORDER BY a.created_at DESC
|
||||
`;
|
||||
console.log('📝 getTeamApps SQL:', sql);
|
||||
@@ -1211,7 +1286,7 @@ export class TeamService extends DatabaseServiceBase {
|
||||
const sql = `
|
||||
SELECT
|
||||
COUNT(*) as totalTeams,
|
||||
COUNT(CASE WHEN is_active = TRUE THEN 1 END) as activeTeams,
|
||||
COUNT(CASE WHEN is_active = 1 THEN 1 END) as activeTeams,
|
||||
COUNT(CASE WHEN is_active = FALSE THEN 1 END) as inactiveTeams,
|
||||
AVG(member_count) as avgMembersPerTeam
|
||||
FROM (
|
||||
@@ -1278,7 +1353,7 @@ export class CompetitionService extends DatabaseServiceBase {
|
||||
|
||||
// 根據名稱獲取競賽
|
||||
static async getCompetitionByName(name: string): Promise<Competition | null> {
|
||||
const sql = 'SELECT * FROM competitions WHERE name = ? AND is_active = TRUE';
|
||||
const sql = 'SELECT * FROM competitions WHERE name = ? AND is_active = 1';
|
||||
return await db.queryOne<Competition>(sql, [name]);
|
||||
}
|
||||
|
||||
@@ -1313,7 +1388,7 @@ export class CompetitionService extends DatabaseServiceBase {
|
||||
|
||||
// 獲取所有競賽
|
||||
static async getAllCompetitions(): Promise<Competition[]> {
|
||||
const sql = 'SELECT * FROM competitions WHERE is_active = TRUE ORDER BY year DESC, month DESC';
|
||||
const sql = 'SELECT * FROM competitions WHERE is_active = 1 ORDER BY year DESC, month DESC';
|
||||
const competitions = await db.query<any>(sql);
|
||||
|
||||
// 轉換字段名稱以匹配前端期望的格式
|
||||
@@ -1365,7 +1440,7 @@ export class CompetitionService extends DatabaseServiceBase {
|
||||
// 獲取當前競賽
|
||||
static async getCurrentCompetition(): Promise<any | null> {
|
||||
try {
|
||||
const sql = 'SELECT * FROM competitions WHERE is_current = TRUE AND is_active = TRUE LIMIT 1';
|
||||
const sql = 'SELECT * FROM competitions WHERE is_current = 1 AND is_active = 1 LIMIT 1';
|
||||
const competitions = await db.query<any>(sql);
|
||||
|
||||
if (competitions.length > 0) {
|
||||
@@ -1485,7 +1560,7 @@ export class CompetitionService extends DatabaseServiceBase {
|
||||
SELECT j.*, cj.assigned_at
|
||||
FROM competition_judges cj
|
||||
JOIN judges j ON cj.judge_id = j.id
|
||||
WHERE cj.competition_id = ? AND j.is_active = TRUE
|
||||
WHERE cj.competition_id = ? AND j.is_active = 1
|
||||
ORDER BY cj.assigned_at ASC
|
||||
`;
|
||||
return await DatabaseServiceBase.safeQuery(sql, [competitionId]);
|
||||
@@ -1546,7 +1621,7 @@ export class CompetitionService extends DatabaseServiceBase {
|
||||
FROM competition_teams ct
|
||||
JOIN teams t ON ct.team_id = t.id
|
||||
LEFT JOIN users u ON t.leader_id = u.id
|
||||
WHERE ct.competition_id = ? AND t.is_active = TRUE
|
||||
WHERE ct.competition_id = ? AND t.is_active = 1
|
||||
ORDER BY ct.registered_at ASC
|
||||
`;
|
||||
return await DatabaseServiceBase.safeQuery(sql, [competitionId]);
|
||||
@@ -1577,7 +1652,7 @@ export class CompetitionService extends DatabaseServiceBase {
|
||||
FROM apps a
|
||||
LEFT JOIN users u ON a.creator_id = u.id
|
||||
LEFT JOIN teams t ON a.team_id = t.id
|
||||
WHERE a.team_id IN (${placeholders}) AND a.is_active = TRUE
|
||||
WHERE a.team_id IN (${placeholders}) AND a.is_active = 1
|
||||
ORDER BY a.created_at ASC
|
||||
`;
|
||||
apps = await DatabaseServiceBase.safeQuery(sql, teamIds);
|
||||
@@ -1590,7 +1665,7 @@ export class CompetitionService extends DatabaseServiceBase {
|
||||
FROM competition_apps ca
|
||||
JOIN apps a ON ca.app_id = a.id
|
||||
LEFT JOIN users u ON a.creator_id = u.id
|
||||
WHERE ca.competition_id = ? AND a.is_active = TRUE
|
||||
WHERE ca.competition_id = ? AND a.is_active = 1
|
||||
ORDER BY ca.submitted_at ASC
|
||||
`;
|
||||
apps = await DatabaseServiceBase.safeQuery(sql, [competitionId]);
|
||||
@@ -1934,7 +2009,7 @@ export class AppService extends DatabaseServiceBase {
|
||||
u.department as creator_department
|
||||
FROM apps a
|
||||
LEFT JOIN users u ON a.creator_id = u.id
|
||||
WHERE a.id = ? AND a.is_active = TRUE
|
||||
WHERE a.id = ? AND a.is_active = 1
|
||||
`;
|
||||
return await this.queryOne(sql, [appId]);
|
||||
}
|
||||
@@ -1962,7 +2037,7 @@ export class AppService extends DatabaseServiceBase {
|
||||
u.department as creator_department
|
||||
FROM apps a
|
||||
LEFT JOIN users u ON a.creator_id = u.id
|
||||
WHERE a.name = ? AND a.is_active = TRUE
|
||||
WHERE a.name = ? AND a.is_active = 1
|
||||
`;
|
||||
return await this.queryOne(sql, [name]);
|
||||
}
|
||||
@@ -1986,7 +2061,7 @@ export class AppService extends DatabaseServiceBase {
|
||||
// 根據狀態篩選
|
||||
if (status && status !== 'all') {
|
||||
if (status === 'active') {
|
||||
whereConditions.push('a.is_active = TRUE');
|
||||
whereConditions.push('a.is_active = 1');
|
||||
} else if (status === 'inactive') {
|
||||
whereConditions.push('a.is_active = FALSE');
|
||||
}
|
||||
@@ -2058,6 +2133,7 @@ export class AppService extends DatabaseServiceBase {
|
||||
app_url?: string;
|
||||
icon?: string;
|
||||
icon_color?: string;
|
||||
department?: string;
|
||||
}): Promise<{ success: boolean; app?: any; error?: string }> {
|
||||
try {
|
||||
const updateFields = [];
|
||||
@@ -2092,20 +2168,41 @@ export class AppService extends DatabaseServiceBase {
|
||||
params.push(updates.icon_color);
|
||||
}
|
||||
|
||||
if (updateFields.length === 0) {
|
||||
// 如果沒有要更新的欄位,檢查是否需要更新部門
|
||||
if (updateFields.length === 0 && !updates.department) {
|
||||
return { success: false, error: '沒有要更新的欄位' };
|
||||
}
|
||||
|
||||
// 如果需要更新部門,先更新創建者的部門
|
||||
if (updates.department) {
|
||||
// 先獲取應用的創建者 ID
|
||||
const appSql = 'SELECT creator_id FROM apps WHERE id = ? AND is_active = 1';
|
||||
const appResult = await this.query(appSql, [appId]);
|
||||
|
||||
if (appResult.length === 0) {
|
||||
return { success: false, error: '應用不存在' };
|
||||
}
|
||||
|
||||
const creatorId = appResult[0].creator_id;
|
||||
|
||||
// 更新創建者的部門
|
||||
const userUpdateSql = 'UPDATE users SET department = ? WHERE id = ?';
|
||||
await this.query(userUpdateSql, [updates.department, creatorId]);
|
||||
}
|
||||
|
||||
// 如果有其他欄位需要更新
|
||||
if (updateFields.length > 0) {
|
||||
updateFields.push('updated_at = NOW()');
|
||||
params.push(appId);
|
||||
|
||||
const sql = `
|
||||
UPDATE apps
|
||||
SET ${updateFields.join(', ')}
|
||||
WHERE id = ? AND is_active = TRUE
|
||||
WHERE id = ? AND is_active = 1
|
||||
`;
|
||||
|
||||
await this.query(sql, params);
|
||||
}
|
||||
|
||||
// 獲取更新後的應用
|
||||
const updatedApp = await this.getAppById(appId);
|
||||
@@ -2173,7 +2270,7 @@ export class AppService extends DatabaseServiceBase {
|
||||
const sql = `
|
||||
SELECT
|
||||
COUNT(*) as total_apps,
|
||||
COUNT(CASE WHEN is_active = TRUE THEN 1 END) as active_apps,
|
||||
COUNT(CASE WHEN is_active = 1 THEN 1 END) as active_apps,
|
||||
COUNT(CASE WHEN is_active = FALSE THEN 1 END) as inactive_apps,
|
||||
COALESCE(SUM(views_count), 0) as total_views,
|
||||
COALESCE(SUM(likes_count), 0) as total_likes,
|
||||
@@ -2605,7 +2702,7 @@ export class AppService extends DatabaseServiceBase {
|
||||
COUNT(DISTINCT a.id) as count
|
||||
FROM apps a
|
||||
JOIN users u ON a.creator_id = u.id
|
||||
WHERE a.is_active = TRUE
|
||||
WHERE a.is_active = 1
|
||||
GROUP BY u.department
|
||||
ORDER BY count DESC, u.department ASC
|
||||
`;
|
||||
@@ -2629,7 +2726,7 @@ export class AppService extends DatabaseServiceBase {
|
||||
type,
|
||||
COUNT(*) as count
|
||||
FROM apps
|
||||
WHERE is_active = TRUE
|
||||
WHERE is_active = 1
|
||||
GROUP BY type
|
||||
ORDER BY count DESC, type ASC
|
||||
`;
|
||||
@@ -2653,7 +2750,7 @@ export class AppService extends DatabaseServiceBase {
|
||||
category,
|
||||
COUNT(*) as count
|
||||
FROM apps
|
||||
WHERE is_active = TRUE
|
||||
WHERE is_active = 1
|
||||
GROUP BY category
|
||||
ORDER BY count DESC, category ASC
|
||||
`;
|
||||
@@ -2847,7 +2944,7 @@ export class AppService extends DatabaseServiceBase {
|
||||
const appStatsSql = `
|
||||
SELECT
|
||||
COUNT(*) as total_apps,
|
||||
COUNT(CASE WHEN is_active = TRUE THEN 1 END) as active_apps,
|
||||
COUNT(CASE WHEN is_active = 1 THEN 1 END) as active_apps,
|
||||
COUNT(CASE WHEN is_active = FALSE THEN 1 END) as inactive_apps,
|
||||
COUNT(CASE WHEN created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) THEN 1 END) as new_apps_this_month,
|
||||
COALESCE(SUM(views_count), 0) as total_views,
|
||||
@@ -2997,7 +3094,7 @@ export class AppService extends DatabaseServiceBase {
|
||||
SELECT COUNT(*) as total
|
||||
FROM user_favorites uf
|
||||
JOIN apps a ON uf.app_id = a.id
|
||||
WHERE uf.user_id = ? AND a.is_active = TRUE
|
||||
WHERE uf.user_id = ? AND a.is_active = 1
|
||||
`;
|
||||
const countResult = await this.queryOne(countSql, [userId]);
|
||||
const total = countResult?.total || 0;
|
||||
@@ -3022,7 +3119,7 @@ export class AppService extends DatabaseServiceBase {
|
||||
FROM user_favorites uf
|
||||
JOIN apps a ON uf.app_id = a.id
|
||||
LEFT JOIN users u ON a.creator_id = u.id
|
||||
WHERE uf.user_id = ? AND a.is_active = TRUE
|
||||
WHERE uf.user_id = ? AND a.is_active = 1
|
||||
ORDER BY uf.created_at DESC
|
||||
LIMIT ${limit} OFFSET ${offset}
|
||||
`;
|
||||
@@ -3334,7 +3431,7 @@ export class AppService extends DatabaseServiceBase {
|
||||
for (const activity of activities) {
|
||||
try {
|
||||
// 嘗試從應用表獲取類別
|
||||
const appSql = 'SELECT type FROM apps WHERE id = ? AND is_active = TRUE';
|
||||
const appSql = 'SELECT type FROM apps WHERE id = ? AND is_active = 1';
|
||||
const appResult = await this.query(appSql, [activity.resource_id]);
|
||||
|
||||
let category = '未分類';
|
||||
|
@@ -3,7 +3,7 @@
|
||||
-- =====================================================
|
||||
|
||||
-- 首先查看現有的團隊數據
|
||||
SELECT id, name FROM teams WHERE is_active = TRUE;
|
||||
SELECT id, name FROM teams WHERE is_active = 1;
|
||||
|
||||
-- 為每個團隊創建對應的虛擬應用記錄
|
||||
-- 格式:team_{teamId} 以便識別這是團隊評分
|
||||
@@ -38,7 +38,7 @@ INSERT INTO apps (
|
||||
0,
|
||||
0,
|
||||
0.00,
|
||||
TRUE,
|
||||
1,
|
||||
NOW(),
|
||||
NOW()
|
||||
);
|
||||
|
Reference in New Issue
Block a user