@@ -5,12 +5,179 @@ import Link from "next/link"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Badge } from "@/components/ui/badge"
import { Sparkles , ArrowLeft , Search , Plus , Filter , X , BarChart3 , Eye , Users } from "lucide-react"
import { Sparkles , ArrowLeft , Search , Plus , Filter , X , BarChart3 , Eye , Users , ChevronLeft , ChevronRight } from "lucide-react"
import WishCard from "@/components/wish-card"
import HeaderMusicControl from "@/components/header-music-control"
import { categories , categorizeWishMultiple , getCategoryStats , type Wish } from "@/lib/categorization"
import { WishService } from "@/lib/supabase-service"
// 分頁組件
interface PaginationProps {
currentPage : number
totalPages : number
onPageChange : ( page : number ) = > void
}
function PaginationComponent ( { currentPage , totalPages , onPageChange } : PaginationProps ) {
// 根據螢幕尺寸調整顯示策略
const getMobilePageNumbers = ( ) = > {
const pages = [ ]
if ( totalPages <= 3 ) {
// 小於等於3頁, 全部顯示
for ( let i = 1 ; i <= totalPages ; i ++ ) {
pages . push ( i )
}
} else {
// 手機端簡化邏輯:只顯示當前頁和相鄰頁
if ( currentPage === 1 ) {
pages . push ( 1 , 2 , '...' , totalPages )
} else if ( currentPage === totalPages ) {
pages . push ( 1 , '...' , totalPages - 1 , totalPages )
} else {
if ( currentPage === 2 ) {
pages . push ( 1 , 2 , 3 , '...' , totalPages )
} else if ( currentPage === totalPages - 1 ) {
pages . push ( 1 , '...' , totalPages - 2 , totalPages - 1 , totalPages )
} else {
pages . push ( 1 , '...' , currentPage , '...' , totalPages )
}
}
}
return pages
}
const getDesktopPageNumbers = ( ) = > {
const pages = [ ]
const maxVisiblePages = 5
if ( totalPages <= maxVisiblePages ) {
for ( let i = 1 ; i <= totalPages ; i ++ ) {
pages . push ( i )
}
} else {
if ( currentPage <= 3 ) {
for ( let i = 1 ; i <= 4 ; i ++ ) {
pages . push ( i )
}
pages . push ( '...' )
pages . push ( totalPages )
} else if ( currentPage >= totalPages - 2 ) {
pages . push ( 1 )
pages . push ( '...' )
for ( let i = totalPages - 3 ; i <= totalPages ; i ++ ) {
pages . push ( i )
}
} else {
pages . push ( 1 )
pages . push ( '...' )
for ( let i = currentPage - 1 ; i <= currentPage + 1 ; i ++ ) {
pages . push ( i )
}
pages . push ( '...' )
pages . push ( totalPages )
}
}
return pages
}
return (
< div className = "flex items-center justify-center gap-1 sm:gap-2 mt-8 px-2 sm:px-4" >
{ /* 上一頁按鈕 */ }
< Button
onClick = { ( ) = > onPageChange ( currentPage - 1 ) }
disabled = { currentPage === 1 }
className = "relative overflow-hidden bg-slate-800/90 hover:bg-slate-700/90 disabled:bg-slate-900/50 disabled:opacity-40 text-slate-200 border border-slate-600/50 hover:border-cyan-400/50 transition-all duration-300 min-w-[36px] sm:min-w-[44px] h-9 sm:h-11 shrink-0"
>
< div className = "absolute inset-0 bg-gradient-to-r from-cyan-500/0 to-blue-500/0 hover:from-cyan-500/10 hover:to-blue-500/10 transition-all duration-300" > < / div >
< ChevronLeft className = "w-3 h-3 sm:w-4 sm:h-4 relative z-10" / >
< / Button >
{ /* 桌面端頁數按鈕 */ }
< div className = "hidden sm:flex items-center gap-1" >
{ getDesktopPageNumbers ( ) . map ( ( page , index ) = > {
if ( page === '...' ) {
return (
< span key = { ` ellipsis- ${ index } ` } className = "px-3 py-2 text-slate-400 text-sm" >
. . .
< / span >
)
}
const pageNumber = page as number
const isActive = pageNumber === currentPage
return (
< Button
key = { pageNumber }
onClick = { ( ) = > onPageChange ( pageNumber ) }
className = { `
relative overflow-hidden min-w-[44px] h-11 text-sm font-medium transition-all duration-300
${ isActive
? 'bg-gradient-to-r from-cyan-500/90 to-blue-600/90 text-white border border-cyan-400/50 shadow-lg shadow-cyan-500/25 transform scale-105'
: 'bg-slate-800/90 hover:bg-slate-700/90 text-slate-200 border border-slate-600/50 hover:border-cyan-400/50'
}
` }
>
{ isActive && (
< div className = "absolute inset-0 bg-gradient-to-r from-cyan-400/20 to-blue-400/20 animate-pulse" > < / div >
) }
< span className = "relative z-10" > { pageNumber } < / span >
< / Button >
)
} ) }
< / div >
{ /* 手機端頁數按鈕 */ }
< div className = "flex sm:hidden items-center gap-0.5" >
{ getMobilePageNumbers ( ) . map ( ( page , index ) = > {
if ( page === '...' ) {
return (
< span key = { ` mobile-ellipsis- ${ index } ` } className = "px-1.5 py-2 text-slate-400 text-xs" >
. . .
< / span >
)
}
const pageNumber = page as number
const isActive = pageNumber === currentPage
return (
< Button
key = { ` mobile- ${ pageNumber } ` }
onClick = { ( ) = > onPageChange ( pageNumber ) }
className = { `
relative overflow-hidden min-w-[32px] h-9 text-xs font-medium transition-all duration-300
${ isActive
? 'bg-gradient-to-r from-cyan-500/90 to-blue-600/90 text-white border border-cyan-400/50 shadow-lg shadow-cyan-500/25'
: 'bg-slate-800/90 hover:bg-slate-700/90 text-slate-200 border border-slate-600/50 hover:border-cyan-400/50'
}
` }
>
{ isActive && (
< div className = "absolute inset-0 bg-gradient-to-r from-cyan-400/20 to-blue-400/20 animate-pulse" > < / div >
) }
< span className = "relative z-10" > { pageNumber } < / span >
< / Button >
)
} ) }
< / div >
{ /* 下一頁按鈕 */ }
< Button
onClick = { ( ) = > onPageChange ( currentPage + 1 ) }
disabled = { currentPage === totalPages }
className = "relative overflow-hidden bg-slate-800/90 hover:bg-slate-700/90 disabled:bg-slate-900/50 disabled:opacity-40 text-slate-200 border border-slate-600/50 hover:border-cyan-400/50 transition-all duration-300 min-w-[36px] sm:min-w-[44px] h-9 sm:h-11 shrink-0"
>
< div className = "absolute inset-0 bg-gradient-to-r from-cyan-500/0 to-blue-500/0 hover:from-cyan-500/10 hover:to-blue-500/10 transition-all duration-300" > < / div >
< ChevronRight className = "w-3 h-3 sm:w-4 sm:h-4 relative z-10" / >
< / Button >
< / div >
)
}
export default function WishesPage() {
const [ wishes , setWishes ] = useState < Wish [ ] > ( [ ] )
const [ publicWishes , setPublicWishes ] = useState < Wish [ ] > ( [ ] )
@@ -21,6 +188,12 @@ export default function WishesPage() {
const [ showFilters , setShowFilters ] = useState ( false )
const [ totalWishes , setTotalWishes ] = useState ( 0 )
const [ privateCount , setPrivateCount ] = useState ( 0 )
// 分頁相關狀態
const [ currentPage , setCurrentPage ] = useState ( 1 )
const [ itemsPerPage ] = useState ( 5 )
const [ paginatedWishes , setPaginatedWishes ] = useState < Wish [ ] > ( [ ] )
const [ totalPages , setTotalPages ] = useState ( 0 )
useEffect ( ( ) = > {
const fetchWishes = async ( ) = > {
@@ -100,6 +273,21 @@ export default function WishesPage() {
setFilteredWishes ( filtered )
} , [ publicWishes , searchTerm , selectedCategories ] )
// 分頁計算 useEffect
useEffect ( ( ) = > {
const startIndex = ( currentPage - 1 ) * itemsPerPage
const endIndex = startIndex + itemsPerPage
const wishesToPaginate = filteredWishes . length > 0 ? filteredWishes : publicWishes
setPaginatedWishes ( wishesToPaginate . slice ( startIndex , endIndex ) )
setTotalPages ( Math . ceil ( wishesToPaginate . length / itemsPerPage ) )
} , [ filteredWishes , publicWishes , currentPage , itemsPerPage ] )
// 重置分頁當篩選條件改變時
useEffect ( ( ) = > {
setCurrentPage ( 1 )
} , [ searchTerm , selectedCategories ] )
const toggleCategory = ( categoryName : string ) = > {
setSelectedCategories ( ( prev ) = >
prev . includes ( categoryName ) ? prev . filter ( ( cat ) = > cat !== categoryName ) : [ . . . prev , categoryName ] ,
@@ -232,16 +420,16 @@ export default function WishesPage() {
< / header >
{ /* Main Content - 手機優化 */ }
< main className = "py-8 md:py-12 px-4" >
< main className = "py-8 md:py-12 px-2 sm: px-4" >
< div className = "container mx-auto max-w-4xl" >
< div className = "text-center mb-6 md:mb-8" >
< h2 className = "text-2xl md:text-3xl font-bold text-white mb-3 md:mb-4" > 聆 聽 每 一 份 真 實 經 歷 < / h2 >
< p className = "text-blue-200 mb-4 md:mb-6 text-sm md:text-base px-4" >
< p className = "text-blue-200 mb-4 md:mb-6 text-sm md:text-base px-2 sm: px-4" >
這 裡 收 集 了 許 多 職 場 工 作 者 願 意 公 開 分 享 的 真 實 困 擾 和 經 驗
< / p >
{ /* Search Bar and Filter Button - 並排布局 */ }
< div className = "flex flex-col sm:flex-row gap-3 max-w-lg mx-auto px-2 md:px-0 mb-4" >
< div className = "flex flex-col sm:flex-row gap-3 max-w-lg mx-auto px-1 sm: px-2 md:px-0 mb-4" >
{ /* Search Input */ }
< div className = "relative flex-1" >
< Search className = "absolute left-3 top-1/2 transform -translate-y-1/2 text-blue-300 w-4 h-4" / >
@@ -277,7 +465,7 @@ export default function WishesPage() {
{ /* Category Filters */ }
{ showFilters && (
< div className = "mb-6 md:mb-8 p-4 md:p-6 bg-slate-800/30 backdrop-blur-sm rounded-xl border border-slate-600/50 animate-in slide-in-from-top-2 duration-200" >
< div className = "mb-6 md:mb-8 p-3 sm: p-4 md:p-6 bg-slate-800/30 backdrop-blur-sm rounded-xl border border-slate-600/50 animate-in slide-in-from-top-2 duration-200 mx-1 sm:mx-0 " >
< div className = "flex items-center justify-between mb-4" >
< h3 className = "text-lg font-semibold text-white flex items-center gap-2" >
< Filter className = "w-5 h-5" / >
@@ -364,12 +552,18 @@ export default function WishesPage() {
< div className = "inline-flex items-center gap-2 bg-slate-800/50 backdrop-blur-sm rounded-full px-3 md:px-4 py-2 text-blue-200 border border-blue-700/50 text-xs md:text-sm" >
< Eye className = "w-3 h-3 md:w-4 md:h-4 text-cyan-400" / >
< span className = "hidden sm:inline" >
公 開 分 享 { publicWishes . length } 個 案 例
{ hasActiveFilters && ` ,找到 ${ filtered Wishes. length } 個相關經歷 ` }
{ hasActiveFilters ? (
< > 找 到 { filteredWishes . length } / { public Wishes . length } 個 相 關 案 例 < / >
) : (
< > 公 開 分 享 { publicWishes . length } 個 案 例 < / >
) }
{ totalPages > 1 && (
< > · 第 { currentPage } / { totalPages } 頁 < / >
) }
< / span >
< span className = "sm:hidden" >
{ publicWishes . length } 個 公 開 案 例
{ hasActiveFilters && ` ( ${ filteredWishes . length } ) ` }
{ hasActiveFilters ? ` ${ filteredWishes . length } / ${ publicWishes. length }` : ` ${ publicWishes . length } 個案例 ` }
{ totalPages > 1 && ` ( ${ currentPage } / ${ totalPages } ) ` }
< / span >
< / div >
@@ -383,21 +577,32 @@ export default function WishesPage() {
< / div >
{ privateCount > 0 && (
< p className = "text-xs md:text-sm text-slate-400 px-4" >
< p className = "text-xs md:text-sm text-slate-400 px-2 sm: px-4" >
私 密 案 例 不 會 顯 示 在 此 頁 面 , 但 會 納 入 問 題 洞 察 分 析 , 幫 助 了 解 整 體 趨 勢
< / p >
) }
< / div >
{ /* Wishes Grid - 手機優化 */ }
{ filter edWishes. length > 0 ? (
< div className = "grid gap-4 md:gap-6 lg:grid-cols-1" >
{ filteredWishes . map ( ( wish ) = > (
< WishCard key = { w ish. id } wish = { wish } / >
) ) }
< / div >
{ paginat edWishes. length > 0 ? (
< >
< div className = "grid gap-4 md:gap-6 lg:grid-cols-1" >
{ paginatedW ishes . map ( ( wish ) = > (
< WishCard key = { wish . id } wish = { wish } / >
) ) }
< / div >
{ /* 分頁組件 */ }
{ totalPages > 1 && (
< PaginationComponent
currentPage = { currentPage }
totalPages = { totalPages }
onPageChange = { setCurrentPage }
/ >
) }
< / >
) : publicWishes . length === 0 ? (
< div className = "text-center py-8 sm:py-12 md:py-16 px-4" >
< div className = "text-center py-8 sm:py-12 md:py-16 px-2 sm: px-4" >
< div className = "mb-4 sm:mb-6" >
< div className = "relative mx-auto w-16 h-20 sm:w-20 sm:h-26 md:w-24 md:h-32" >
< div className = "absolute bottom-0 left-1/2 transform -translate-x-1/2 w-14 h-20 md:w-16 md:h-24 bg-gradient-to-b from-cyan-100/20 to-blue-200/30 rounded-t-xl md:rounded-t-2xl rounded-b-md md:rounded-b-lg shadow-xl shadow-cyan-500/20 backdrop-blur-sm border border-cyan-300/30 opacity-50" >
@@ -409,7 +614,7 @@ export default function WishesPage() {
< h3 className = "text-base sm:text-lg md:text-xl font-semibold text-blue-100 mb-2" >
{ totalWishes > 0 ? "還沒有人公開分享經歷" : "還沒有人分享經歷" }
< / h3 >
< p className = "text-blue-300 mb-4 sm:mb-6 text-sm sm:text-base leading-relaxed px-2" >
< p className = "text-blue-300 mb-4 sm:mb-6 text-sm sm:text-base leading-relaxed px-1 sm: px-2" >
{ totalWishes > 0
? ` 目前有 ${ totalWishes } 個案例,但都選擇保持私密。成為第一個公開分享的人吧! `
: "成為第一個分享工作困擾的人,幫助更多人找到解決方案" }
@@ -422,10 +627,10 @@ export default function WishesPage() {
< / Link >
< / div >
) : (
< div className = "text-center py-8 sm:py-12 md:py-16 px-4" >
< div className = "text-center py-8 sm:py-12 md:py-16 px-2 sm: px-4" >
< Search className = "w-10 h-10 sm:w-12 sm:h-12 md:w-16 md:h-16 text-blue-400 mx-auto mb-3 sm:mb-4 opacity-50" / >
< h3 className = "text-base sm:text-lg md:text-xl font-semibold text-blue-100 mb-2" > 沒 有 找 到 相 關 案 例 < / h3 >
< p className = "text-blue-300 mb-4 md:mb-6 text-sm md:text-base" >
< p className = "text-blue-300 mb-4 md:mb-6 text-sm md:text-base px-1 sm:px-0 " >
{ hasActiveFilters ? "試試調整篩選條件,或分享你的獨特經歷" : "試試其他關鍵字,或分享你的困擾" }
< / p >
< div className = "flex flex-col sm:flex-row gap-3 md:gap-4 justify-center" >