@@ -4,6 +4,15 @@ import type { GpaScoreWithUser, VerifyStatus } from "@/types/scores";
44import { ScoreVerifyButton } from "../ScoreVerifyButton" ;
55import { format } from "date-fns" ;
66import { toast } from "sonner" ;
7+ import { StatusBadge } from "../StatusBadge" ;
8+ import {
9+ Table ,
10+ TableBody ,
11+ TableCell ,
12+ TableHead ,
13+ TableHeader ,
14+ TableRow ,
15+ } from "@/components/ui/table" ;
716
817interface Props {
918 verifyFilter : VerifyStatus ;
@@ -86,150 +95,153 @@ export function GpaScoreTable({ verifyFilter }: Props) {
8695 }
8796 } ;
8897
89- if ( loading ) return < div > 로딩중...</ div > ;
90-
9198 return (
92- < div className = "overflow-x-auto" >
93- < table className = "min-w-full divide-y divide-gray-200" >
94- < thead className = "bg-gray-50" >
95- < tr >
96- < th className = "px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500" >
97- ID
98- </ th >
99- < th className = "px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500" >
100- 닉네임
101- </ th >
102- < th className = "px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500" >
103- GPA
104- </ th >
105- < th className = "px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500" >
106- 기준점수
107- </ th >
108- < th className = "px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500" >
109- 상태
110- </ th >
111- < th className = "px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500" >
112- 제출일
113- </ th >
114- < th className = "px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500" >
115- 거절사유
116- </ th >
117- < th className = "px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500" >
118- 인증파일
119- </ th >
120- < th className = "px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500" >
121- 작업
122- </ th >
123- </ tr >
124- </ thead >
125- < tbody className = "divide-y divide-gray-200 bg-white" >
126- { scores . map ( ( score ) => (
127- < tr key = { score . gpaScoreStatusResponse . id } >
128- < td className = "whitespace-nowrap px-6 py-4" >
129- { score . gpaScoreStatusResponse . id }
130- </ td >
131- < td className = "whitespace-nowrap px-6 py-4" >
132- < div className = "flex items-center" >
133- < img
134- src = { score . siteUserResponse . profileImageUrl }
135- alt = "프로필"
136- className = "mr-2 h-8 w-8 rounded-full"
137- />
138- { score . siteUserResponse . nickname }
139- </ div >
140- </ td >
141- < td className = "whitespace-nowrap px-6 py-4" >
142- { editingId === score . gpaScoreStatusResponse . id ? (
143- < div className = "flex gap-2" >
144- < input
145- type = "number"
146- step = "0.01"
147- value = { editingGpa }
148- onChange = { ( e ) =>
149- setEditingGpa ( parseFloat ( e . target . value ) )
150- }
151- className = "w-20 rounded border px-2 py-1"
152- />
99+ < div className = "rounded-lg border bg-white shadow" >
100+ < div className = "overflow-x-auto" >
101+ < Table >
102+ < TableHeader >
103+ < TableRow >
104+ < TableHead > ID</ TableHead >
105+ < TableHead > 닉네임</ TableHead >
106+ < TableHead > GPA</ TableHead >
107+ < TableHead > 기준점수</ TableHead >
108+ < TableHead > 상태</ TableHead >
109+ < TableHead > 제출일</ TableHead >
110+ < TableHead > 거절사유</ TableHead >
111+ < TableHead > 인증파일</ TableHead >
112+ < TableHead > 작업</ TableHead >
113+ </ TableRow >
114+ </ TableHeader >
115+ < TableBody >
116+ { loading ? (
117+ < TableRow >
118+ < TableCell colSpan = { 9 } className = "text-center" >
119+ < div className = "flex items-center justify-center" >
120+ < div className = "h-5 w-5 animate-spin rounded-full border-b-2 border-gray-900" > </ div >
121+ < span className = "ml-2" > 로딩중...</ span >
153122 </ div >
154- ) : (
155- score . gpaScoreStatusResponse . gpaResponse . gpa
156- ) }
157- </ td >
158- < td className = "whitespace-nowrap px-6 py-4" >
159- { editingId === score . gpaScoreStatusResponse . id ? (
160- < div className = "flex gap-2" >
161- < input
162- type = "number"
163- step = "0.01"
164- value = { editingGpaCriteria }
165- onChange = { ( e ) =>
166- setEditingGpaCriteria ( parseFloat ( e . target . value ) )
167- }
168- className = "w-20 rounded border px-2 py-1"
123+ </ TableCell >
124+ </ TableRow >
125+ ) : scores . length === 0 ? (
126+ < TableRow >
127+ < TableCell colSpan = { 9 } className = "text-center text-gray-500" >
128+ 데이터가 없습니다
129+ </ TableCell >
130+ </ TableRow >
131+ ) : (
132+ scores . map ( ( score ) => (
133+ < TableRow
134+ key = { score . gpaScoreStatusResponse . id }
135+ className = "hover:bg-gray-50"
136+ >
137+ < TableCell > { score . gpaScoreStatusResponse . id } </ TableCell >
138+ < TableCell >
139+ < div className = "flex items-center" >
140+ < img
141+ src = { score . siteUserResponse . profileImageUrl }
142+ alt = "프로필"
143+ className = "mr-2 h-8 w-8 rounded-full"
144+ />
145+ { score . siteUserResponse . nickname }
146+ </ div >
147+ </ TableCell >
148+ < TableCell >
149+ { editingId === score . gpaScoreStatusResponse . id ? (
150+ < div className = "flex gap-2" >
151+ < input
152+ type = "number"
153+ step = "0.01"
154+ value = { editingGpa }
155+ onChange = { ( e ) =>
156+ setEditingGpa ( parseFloat ( e . target . value ) )
157+ }
158+ className = "w-20 rounded border px-2 py-1"
159+ />
160+ </ div >
161+ ) : (
162+ score . gpaScoreStatusResponse . gpaResponse . gpa
163+ ) }
164+ </ TableCell >
165+ < TableCell >
166+ { editingId === score . gpaScoreStatusResponse . id ? (
167+ < div className = "flex gap-2" >
168+ < input
169+ type = "number"
170+ step = "0.01"
171+ value = { editingGpaCriteria }
172+ onChange = { ( e ) =>
173+ setEditingGpaCriteria ( parseFloat ( e . target . value ) )
174+ }
175+ className = "w-20 rounded border px-2 py-1"
176+ />
177+ < button
178+ onClick = { ( ) => handleSave ( score ) }
179+ className = "rounded bg-blue-500 px-2 py-1 text-white hover:bg-blue-600"
180+ >
181+ 저장
182+ </ button >
183+ < button
184+ onClick = { ( ) => setEditingId ( null ) }
185+ className = "rounded bg-gray-500 px-2 py-1 text-white hover:bg-gray-600"
186+ >
187+ 취소
188+ </ button >
189+ </ div >
190+ ) : (
191+ < div className = "flex gap-2" >
192+ { score . gpaScoreStatusResponse . gpaResponse . gpaCriteria }
193+ < button
194+ onClick = { ( ) => handleEdit ( score ) }
195+ className = "rounded bg-gray-100 px-2 py-1 text-gray-600 hover:bg-gray-200"
196+ >
197+ 수정
198+ </ button >
199+ </ div >
200+ ) }
201+ </ TableCell >
202+ < TableCell >
203+ < StatusBadge
204+ status = { score . gpaScoreStatusResponse . verifyStatus }
169205 />
170- < button
171- onClick = { ( ) => handleSave ( score ) }
172- className = "rounded bg-blue-500 px-2 py-1 text-white hover:bg-blue-600"
173- >
174- 저장
175- </ button >
176- < button
177- onClick = { ( ) => setEditingId ( null ) }
178- className = "rounded bg-gray-500 px-2 py-1 text-white hover:bg-gray-600"
206+ </ TableCell >
207+ < TableCell >
208+ { format (
209+ new Date ( score . gpaScoreStatusResponse . createdAt ) ,
210+ "yyyy-MM-dd HH:mm" ,
211+ ) }
212+ </ TableCell >
213+ < TableCell >
214+ { score . gpaScoreStatusResponse . rejectedReason || "-" }
215+ </ TableCell >
216+ < TableCell >
217+ < a
218+ href = { `${ S3_BASE_URL } ${ score . gpaScoreStatusResponse . gpaResponse . gpaReportUrl } ` }
219+ target = "_blank"
220+ rel = "noopener noreferrer"
221+ className = "text-blue-600 hover:text-blue-800 hover:underline"
179222 >
180- 취소
181- </ button >
182- </ div >
183- ) : (
184- < div className = "flex gap-2" >
185- { score . gpaScoreStatusResponse . gpaResponse . gpaCriteria }
186- < button
187- onClick = { ( ) => handleEdit ( score ) }
188- className = "rounded bg-gray-100 px-2 py-1 text-gray-600 hover:bg-gray-200"
189- >
190- 수정
191- </ button >
192- </ div >
193- ) }
194- </ td >
195- < td className = "whitespace-nowrap px-6 py-4" >
196- { score . gpaScoreStatusResponse . verifyStatus }
197- </ td >
198- < td className = "whitespace-nowrap px-6 py-4" >
199- { format (
200- new Date ( score . gpaScoreStatusResponse . createdAt ) ,
201- "yyyy-MM-dd HH:mm" ,
202- ) }
203- </ td >
204- < td className = "whitespace-nowrap px-6 py-4" >
205- { score . gpaScoreStatusResponse . rejectedReason || "-" }
206- </ td >
207- < td className = "whitespace-nowrap px-6 py-4" >
208- < a
209- href = { `${ S3_BASE_URL } ${ score . gpaScoreStatusResponse . gpaResponse . gpaReportUrl } ` }
210- target = "_blank"
211- rel = "noopener noreferrer"
212- className = "text-blue-600 hover:text-blue-800 hover:underline"
213- >
214- 파일 보기
215- </ a >
216- </ td >
217- < td className = "whitespace-nowrap px-6 py-4" >
218- < ScoreVerifyButton
219- currentStatus = { score . gpaScoreStatusResponse . verifyStatus }
220- onVerifyChange = { ( status , reason ) =>
221- handleVerifyStatus (
222- score . gpaScoreStatusResponse . id ,
223- status ,
224- reason ,
225- )
226- }
227- />
228- </ td >
229- </ tr >
230- ) ) }
231- </ tbody >
232- </ table >
223+ 파일 보기
224+ </ a >
225+ </ TableCell >
226+ < TableCell >
227+ < ScoreVerifyButton
228+ currentStatus = { score . gpaScoreStatusResponse . verifyStatus }
229+ onVerifyChange = { ( status , reason ) =>
230+ handleVerifyStatus (
231+ score . gpaScoreStatusResponse . id ,
232+ status ,
233+ reason ,
234+ )
235+ }
236+ />
237+ </ TableCell >
238+ </ TableRow >
239+ ) )
240+ ) }
241+ </ TableBody >
242+ </ Table >
243+ </ div >
244+ { /* 페이지네이션 추가 예정 */ }
233245 </ div >
234246 ) ;
235247}
0 commit comments