11import Link from "next/link" ;
22
3- import { requireApproved } from "@/lib/guard" ;
4- import { listSessions } from "@/lib/sessions" ;
53import { Button } from "@/components/ui/button" ;
4+ import { requireApproved } from "@/lib/guard" ;
5+ import {
6+ listApprovedMembers ,
7+ listSessions ,
8+ type SessionFilters ,
9+ } from "@/lib/sessions" ;
610
711import { EmptyState } from "./_components/EmptyState" ;
12+ import { FilterBar } from "./_components/FilterBar" ;
813import { SessionCard } from "./_components/SessionCard" ;
914
1015export const dynamic = "force-dynamic" ;
1116
17+ type SearchParams = {
18+ cursor ?: string ;
19+ q ?: string ;
20+ member ?: string ;
21+ month ?: string ;
22+ pmin ?: string ;
23+ pmax ?: string ;
24+ } ;
25+
1226type PageProps = {
13- searchParams : Promise < { cursor ?: string } > ;
27+ searchParams : Promise < SearchParams > ;
1428} ;
1529
1630export default async function SessionsArchivePage ( { searchParams } : PageProps ) {
1731 await requireApproved ( ) ;
1832
19- const { cursor } = await searchParams ;
20- const cursorId = parseCursor ( cursor ) ;
33+ const sp = await searchParams ;
34+ const cursorId = parseCursor ( sp . cursor ) ;
35+ const filters : SessionFilters = {
36+ q : sp . q ?. trim ( ) || undefined ,
37+ memberId : sp . member ?. trim ( ) || undefined ,
38+ month : sp . month ?. trim ( ) || undefined ,
39+ pmin : parsePositiveInt ( sp . pmin ) ,
40+ pmax : parsePositiveInt ( sp . pmax ) ,
41+ } ;
42+ const hasActiveFilters = Boolean (
43+ filters . q ||
44+ filters . memberId ||
45+ filters . month ||
46+ filters . pmin != null ||
47+ filters . pmax != null ,
48+ ) ;
49+
50+ const [ page , members ] = await Promise . all ( [
51+ listSessions ( { cursorId, filters } ) ,
52+ listApprovedMembers ( ) ,
53+ ] ) ;
2154
22- const page = await listSessions ( { cursorId } ) ;
55+ const nextHref =
56+ page . nextCursorId != null
57+ ? buildHref ( { ...sp , cursor : String ( page . nextCursorId ) } )
58+ : null ;
2359
2460 return (
2561 < main className = "container mx-auto max-w-2xl p-6" >
@@ -36,8 +72,22 @@ export default async function SessionsArchivePage({ searchParams }: PageProps) {
3672 </ Link >
3773 </ header >
3874
75+ < FilterBar
76+ q = { sp . q ?? "" }
77+ memberId = { sp . member ?? "" }
78+ month = { sp . month ?? "" }
79+ pmin = { sp . pmin ?? "" }
80+ pmax = { sp . pmax ?? "" }
81+ members = { members }
82+ hasActiveFilters = { hasActiveFilters }
83+ />
84+
3985 { page . items . length === 0 ? (
40- < EmptyState />
86+ hasActiveFilters ? (
87+ < EmptyFilterResult />
88+ ) : (
89+ < EmptyState />
90+ )
4191 ) : (
4292 < >
4393 < ul className = "flex flex-col gap-4" >
@@ -48,9 +98,9 @@ export default async function SessionsArchivePage({ searchParams }: PageProps) {
4898 ) ) }
4999 </ ul >
50100
51- { page . nextCursorId != null && (
101+ { nextHref && (
52102 < div className = "mt-6 flex justify-center" >
53- < Link href = { `/sessions?cursor= ${ page . nextCursorId } ` } >
103+ < Link href = { nextHref } >
54104 < Button variant = "outline" > 더 보기</ Button >
55105 </ Link >
56106 </ div >
@@ -61,8 +111,43 @@ export default async function SessionsArchivePage({ searchParams }: PageProps) {
61111 ) ;
62112}
63113
114+ function EmptyFilterResult ( ) {
115+ return (
116+ < section className = "flex flex-col items-center gap-3 rounded-md border border-dashed p-10 text-center" >
117+ < p className = "text-sm text-muted-foreground" >
118+ 조건에 맞는 세션이 없어요.
119+ </ p >
120+ < Link
121+ href = "/sessions"
122+ className = "text-xs underline underline-offset-4"
123+ >
124+ 필터 초기화
125+ </ Link >
126+ </ section >
127+ ) ;
128+ }
129+
64130function parseCursor ( raw : string | undefined ) : number | undefined {
65131 if ( ! raw ) return undefined ;
66132 const n = Number . parseInt ( raw , 10 ) ;
67133 return Number . isFinite ( n ) && n > 0 ? n : undefined ;
68134}
135+
136+ function parsePositiveInt ( raw : string | undefined ) : number | undefined {
137+ if ( raw == null || raw === "" ) return undefined ;
138+ const n = Number . parseInt ( raw , 10 ) ;
139+ if ( ! Number . isFinite ( n ) || n < 0 ) return undefined ;
140+ return n ;
141+ }
142+
143+ /**
144+ * 기존 searchParams 를 유지하면서 cursor 만 갈아끼운 URL.
145+ */
146+ function buildHref ( params : Record < string , string | undefined > ) : string {
147+ const q = new URLSearchParams ( ) ;
148+ for ( const [ k , v ] of Object . entries ( params ) ) {
149+ if ( v != null && v !== "" ) q . set ( k , v ) ;
150+ }
151+ const s = q . toString ( ) ;
152+ return s ? `/sessions?${ s } ` : "/sessions" ;
153+ }
0 commit comments