實作前台 APP 呈現、APP 詳細頁面

This commit is contained in:
2025-09-10 00:27:26 +08:00
parent 900e33aefa
commit bc2104d374
17 changed files with 318 additions and 1565 deletions

View File

@@ -1,6 +1,6 @@
"use client"
import { useState } from "react"
import { useState, useEffect } from "react"
import { useAuth } from "@/contexts/auth-context"
import { useCompetition } from "@/contexts/competition-context"
import {
@@ -47,8 +47,7 @@ import { AwardDetailDialog } from "@/components/competition/award-detail-dialog"
import { LikeButton } from "@/components/like-button"
// AI applications data - empty for production
const aiApps: any[] = []
// AI applications data - will be loaded from API
// Pagination component
interface PaginationProps {
@@ -156,6 +155,15 @@ export default function AIShowcasePlatform() {
const [showCompetition, setShowCompetition] = useState(false)
const appsPerPage = 6
// 新增狀態管理
const [aiApps, setAiApps] = useState<any[]>([])
const [isLoadingApps, setIsLoadingApps] = useState(true)
const [totalPages, setTotalPages] = useState(0)
const [totalApps, setTotalApps] = useState(0)
const [departments, setDepartments] = useState<{value: string, label: string, count: number}[]>([])
const [types, setTypes] = useState<{value: string, label: string, count: number}[]>([])
const [categories, setCategories] = useState<{value: string, label: string, count: number}[]>([])
// Competition page states
const [selectedCompetitionTypeFilter, setSelectedCompetitionTypeFilter] = useState("all")
const [selectedMonthFilter, setSelectedMonthFilter] = useState("all")
@@ -170,21 +178,61 @@ export default function AIShowcasePlatform() {
const [showAwardDetail, setShowAwardDetail] = useState(false)
const [selectedAward, setSelectedAward] = useState<any>(null)
const filteredApps = aiApps.filter((app) => {
const matchesSearch =
app.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
app.description.toLowerCase().includes(searchTerm.toLowerCase())
const matchesDepartment = selectedDepartment === "all" || app.department === selectedDepartment
const matchesType = selectedType === "all" || app.type === selectedType
// 載入應用數據
const loadApps = async () => {
try {
setIsLoadingApps(true)
const params = new URLSearchParams({
page: currentPage.toString(),
limit: appsPerPage.toString(),
search: searchTerm,
department: selectedDepartment,
type: selectedType
})
return matchesSearch && matchesDepartment && matchesType
})
const response = await fetch(`/api/apps?${params}`)
const data = await response.json()
// Pagination logic
const totalPages = Math.ceil(filteredApps.length / appsPerPage)
const startIndex = (currentPage - 1) * appsPerPage
const endIndex = startIndex + appsPerPage
const currentApps = filteredApps.slice(startIndex, endIndex)
if (data.success) {
setAiApps(data.data.apps)
setTotalPages(data.data.pagination.totalPages)
setTotalApps(data.data.pagination.total)
setDepartments(data.data.departments || [])
} else {
console.error('載入應用數據失敗:', data.error)
setAiApps([])
}
} catch (error) {
console.error('載入應用數據錯誤:', error)
setAiApps([])
} finally {
setIsLoadingApps(false)
}
}
// 載入篩選選項
const loadFilterOptions = async () => {
try {
const response = await fetch('/api/apps?page=1&limit=1')
const data = await response.json()
if (data.success) {
setDepartments(data.data.departments || [])
// 可以添加更多篩選選項的載入
}
} catch (error) {
console.error('載入篩選選項錯誤:', error)
}
}
// 初始載入
useEffect(() => {
loadApps()
loadFilterOptions()
}, [currentPage, searchTerm, selectedDepartment, selectedType])
// 當前顯示的應用(已經由 API 處理分頁和篩選)
const currentApps = aiApps
// Reset to first page when filters change
const handleFilterChange = (filterType: string, value: string) => {
@@ -201,6 +249,40 @@ export default function AIShowcasePlatform() {
setSearchTerm(value)
}
const handlePageChange = (page: number) => {
setCurrentPage(page)
}
// 圖標映射函數
const getIconComponent = (iconName: string) => {
const iconMap: { [key: string]: any } = {
'Bot': Brain,
'ImageIcon': ImageIcon,
'Mic': Mic,
'MessageSquare': MessageSquare,
'Settings': Settings,
'Zap': Zap,
'TrendingUp': TrendingUp,
'Star': Star,
'Heart': Heart,
'Eye': Eye,
'Trophy': Trophy,
'Award': Award,
'Medal': Medal,
'Target': Target,
'Users': Users,
'Lightbulb': Lightbulb,
'Search': Search,
'Plus': Plus,
'X': X,
'ChevronLeft': ChevronLeft,
'ChevronRight': ChevronRight,
'ArrowLeft': ArrowLeft
}
return iconMap[iconName] || Brain // 預設使用 Brain 圖標
}
const getTypeColor = (type: string) => {
const colors = {
: "bg-blue-100 text-blue-800 border-blue-200",
@@ -866,19 +948,30 @@ export default function AIShowcasePlatform() {
{/* Results summary */}
<div className="mt-4 text-sm text-gray-600">
{filteredApps.length} {currentPage} {totalPages}
{totalApps} {currentPage} {totalPages}
</div>
</div>
{/* All Apps with Pagination */}
<div>
<h3 className="text-2xl font-bold text-gray-900 mb-6"> ({filteredApps.length})</h3>
<h3 className="text-2xl font-bold text-gray-900 mb-6">
({totalApps})
{isLoadingApps && <span className="text-sm text-gray-500 ml-2">...</span>}
</h3>
{currentApps.length > 0 ? (
{isLoadingApps ? (
<div className="text-center py-12">
<div className="text-gray-400 mb-4">
<Search className="w-16 h-16 mx-auto animate-pulse" />
</div>
<h3 className="text-xl font-semibold text-gray-600 mb-2">...</h3>
<p className="text-gray-500"></p>
</div>
) : currentApps.length > 0 ? (
<>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{currentApps.map((app) => {
const IconComponent = app.icon
const IconComponent = getIconComponent(app.icon || 'Bot')
const likes = getAppLikes(app.id.toString())
const views = getViewCount(app.id.toString())
const rating = getAppRating(app.id.toString())
@@ -944,7 +1037,7 @@ export default function AIShowcasePlatform() {
{/* Pagination */}
{totalPages > 1 && (
<Pagination currentPage={currentPage} totalPages={totalPages} onPageChange={setCurrentPage} />
<Pagination currentPage={currentPage} totalPages={totalPages} onPageChange={handlePageChange} />
)}
</>
) : (