@@ -288,117 +288,125 @@ export default function AdminUsersPage() {
288288 </ TableColumn > ) }
289289 </ TableHeader >
290290
291- < TableBody items = { rows } isLoading = { loading } loadingContent = { < Spinner label = "불러오는 중..." /> }
292- emptyContent = { err || '데이터가 없습니다.' } >
293- { ( user ) => ( < TableRow key = { user . id } className = "hover:bg-[#35353b99]" >
294- { /* NAME */ }
295- < TableCell >
296- < span className = "font-medium mr-2" > { user . name } </ span >
297- < Chip size = "sm" variant = "flat" color = { roleColor ( user . userRole ) } >
298- { user . userRole }
299- </ Chip >
300- </ TableCell >
301-
302- { /* MAJOR */ }
303- < TableCell > { user . major } </ TableCell >
304-
305- { /* STUDENT ID */ }
306- < TableCell > { user . studentId } </ TableCell >
307-
308- { /* EMAIL */ }
309- < TableCell >
310- < span className = "text-sm" > { user . email } </ span >
311- </ TableCell >
312-
313- { /* ROLE */ }
314- < TableCell >
315- < div className = "flex items-center gap-3" >
316- < Select
317- aria-label = "역할 수정"
318- selectedKeys = { new Set ( [ user . userRole || '' ] ) }
319- onSelectionChange = { ( keys ) => {
320- const nextRole = String ( Array . from ( keys ) [ 0 ] || user . userRole ) ;
321- if ( nextRole !== user . userRole ) {
322- const ok = confirm ( `역할을 '${ user . userRole } ' → '${ nextRole } ' 로 변경할까요?` ) ;
323- if ( ok ) void patchSmart ( { user, nextRole, nextTeam : user . team ?? null } ) ;
324- }
325- } }
326- size = "sm"
327- className = "min-w-[140px]"
328- classNames = { {
329- trigger : 'bg-zinc-900 text-white border border-zinc-700 data-[hover=true]:bg-zinc-800' ,
330- value : 'text-white' ,
331- popoverContent : 'bg-zinc-900 border border-zinc-700' ,
332- listbox : 'text-white' ,
333- selectorIcon : 'text-zinc-400' ,
334- } }
335- itemClasses = { {
336- base : 'rounded-md data-[hover=true]:bg-zinc-800 data-[focus=true]:bg-zinc-800' ,
337- title : 'text-white' ,
338- } }
339- >
340- { ROLE_OPTIONS . map ( ( r ) => ( < SelectItem key = { r } value = { r } >
341- { r }
342- </ SelectItem > ) ) }
343- </ Select >
344- </ div >
345- </ TableCell >
346-
347- { /* TEAM */ }
348- < TableCell >
349- < Select
350- aria-label = "팀 수정"
351- selectedKeys = { new Set ( [ user . team || '' ] ) }
352- onSelectionChange = { ( keys ) => {
353- const k = String ( Array . from ( keys ) [ 0 ] ?? '' ) ;
354- const nextTeam = k === '' ? null : k ; // 서버에는 enum name로 전송
355- if ( ( nextTeam ?? null ) !== ( user . team ?? null ) ) {
356- const old = user . team ? TEAM_LABEL [ user . team ] || user . team : '(없음)' ;
357- const neu = nextTeam ? TEAM_LABEL [ nextTeam ] || nextTeam : '(없음)' ;
358- const ok = confirm ( `팀을 '${ old } ' → '${ neu } ' 로 변경할까요?` ) ;
359- if ( ok ) void patchSmart ( { user, nextRole : user . userRole , nextTeam} ) ;
360- }
361- } }
362- size = "sm"
363- className = "min-w-[160px]"
364- classNames = { {
365- trigger : 'bg-zinc-900 text-white border border-zinc-700 data-[hover=true]:bg-zinc-800' ,
366- value : 'text-white' ,
367- popoverContent : 'bg-zinc-900 border border-zinc-700' ,
368- listbox : 'text-white' ,
369- selectorIcon : 'text-zinc-400' ,
370- } }
371- itemClasses = { {
372- base : 'rounded-md data-[hover=true]:bg-zinc-800 data-[focus=true]:bg-zinc-800' ,
373- title : 'text-white' ,
374- } }
375- >
376- < SelectItem key = "" value = "" >
377- (없음)
378- </ SelectItem >
379- { TEAM_ENUM_VALUES . map ( ( t ) => ( < SelectItem key = { t } value = { t } >
380- { TEAM_LABEL [ t ] }
381- </ SelectItem > ) ) }
382- </ Select >
383- </ TableCell >
384-
385- { /* ACTIONS (Delete) */ }
386- { canRenderDelete ? ( < TableCell >
387- < div className = "flex justify-end" >
388- { user . id !== me ?. id ? ( < Button
389- size = "sm"
390- color = "danger"
391- variant = "flat"
392- onClick = { ( e ) => {
393- e . stopPropagation ( ) ;
394- void handleDelete ( user ) ;
395- } }
396- >
397- 삭제
398- </ Button > ) : null }
399- </ div >
400- </ TableCell > ) : null }
401- </ TableRow > ) }
291+ < TableBody
292+ items = { rows }
293+ isLoading = { loading }
294+ loadingContent = { < Spinner label = "불러오는 중..." /> }
295+ emptyContent = { err || '데이터가 없습니다.' }
296+ >
297+ { ( user ) => {
298+ const cells = [ // NAME
299+ ( < TableCell key = "name" >
300+ < span className = "font-medium mr-2" > { user . name } </ span >
301+ < Chip size = "sm" variant = "flat" color = { roleColor ( user . userRole ) } >
302+ { user . userRole }
303+ </ Chip >
304+ </ TableCell > ) ,
305+
306+ // MAJOR
307+ < TableCell key = "major" > { user . major } </ TableCell > ,
308+
309+ // STUDENT ID
310+ < TableCell key = "studentId" > { user . studentId } </ TableCell > ,
311+
312+ // EMAIL
313+ ( < TableCell key = "email" >
314+ < span className = "text-sm" > { user . email } </ span >
315+ </ TableCell > ) ,
316+
317+ // ROLE
318+ ( < TableCell key = "role" >
319+ < div className = "flex items-center gap-3" >
320+ < Select
321+ aria-label = "역할 수정"
322+ selectedKeys = { new Set ( [ user . userRole || '' ] ) }
323+ onSelectionChange = { ( keys ) => {
324+ const nextRole = String ( Array . from ( keys ) [ 0 ] || user . userRole ) ;
325+ if ( nextRole !== user . userRole ) {
326+ const ok = confirm ( `역할을 '${ user . userRole } ' → '${ nextRole } ' 로 변경할까요?` ) ;
327+ if ( ok ) void patchSmart ( { user, nextRole, nextTeam : user . team ?? null } ) ;
328+ }
329+ } }
330+ size = "sm"
331+ className = "min-w-[140px]"
332+ classNames = { {
333+ trigger : 'bg-zinc-900 text-white border border-zinc-700 data-[hover=true]:bg-zinc-800' ,
334+ value : 'text-white' ,
335+ popoverContent : 'bg-zinc-900 border border-zinc-700' ,
336+ listbox : 'text-white' ,
337+ selectorIcon : 'text-zinc-400' ,
338+ } }
339+ itemClasses = { {
340+ base : 'rounded-md data-[hover=true]:bg-zinc-800 data-[focus=true]:bg-zinc-800' ,
341+ title : 'text-white' ,
342+ } }
343+ >
344+ { ROLE_OPTIONS . map ( ( r ) => ( < SelectItem key = { r } value = { r } >
345+ { r }
346+ </ SelectItem > ) ) }
347+ </ Select >
348+ </ div >
349+ </ TableCell > ) ,
350+
351+ // TEAM
352+ ( < TableCell key = "team" >
353+ < Select
354+ aria-label = "팀 수정"
355+ selectedKeys = { new Set ( [ user . team || '' ] ) }
356+ onSelectionChange = { ( keys ) => {
357+ const k = String ( Array . from ( keys ) [ 0 ] ?? '' ) ;
358+ const nextTeam = k === '' ? null : k ; // 서버에는 enum name 전송
359+ if ( ( nextTeam ?? null ) !== ( user . team ?? null ) ) {
360+ const old = user . team ? TEAM_LABEL [ user . team ] || user . team : '(없음)' ;
361+ const neu = nextTeam ? TEAM_LABEL [ nextTeam ] || nextTeam : '(없음)' ;
362+ const ok = confirm ( `팀을 '${ old } ' → '${ neu } ' 로 변경할까요?` ) ;
363+ if ( ok ) void patchSmart ( { user, nextRole : user . userRole , nextTeam} ) ;
364+ }
365+ } }
366+ size = "sm"
367+ className = "min-w-[160px]"
368+ classNames = { {
369+ trigger : 'bg-zinc-900 text-white border border-zinc-700 data-[hover=true]:bg-zinc-800' ,
370+ value : 'text-white' ,
371+ popoverContent : 'bg-zinc-900 border border-zinc-700' ,
372+ listbox : 'text-white' ,
373+ selectorIcon : 'text-zinc-400' ,
374+ } }
375+ itemClasses = { {
376+ base : 'rounded-md data-[hover=true]:bg-zinc-800 data-[focus=true]:bg-zinc-800' ,
377+ title : 'text-white' ,
378+ } }
379+ >
380+ < SelectItem key = "" value = "" >
381+ (없음)
382+ </ SelectItem >
383+ { TEAM_ENUM_VALUES . map ( ( t ) => ( < SelectItem key = { t } value = { t } >
384+ { TEAM_LABEL [ t ] }
385+ </ SelectItem > ) ) }
386+ </ Select >
387+ </ TableCell > ) ,
388+
389+ // ACTIONS (조건부)
390+ canRenderDelete && ( < TableCell key = "actions" >
391+ < div className = "flex justify-end" >
392+ { user . id !== me ?. id ? ( < Button
393+ size = "sm"
394+ color = "danger"
395+ variant = "flat"
396+ onClick = { ( e ) => {
397+ e . stopPropagation ( ) ;
398+ void handleDelete ( user ) ;
399+ } }
400+ >
401+ 삭제
402+ </ Button > ) : null }
403+ </ div >
404+ </ TableCell > ) , ] . filter ( Boolean ) ;
405+
406+ return ( < TableRow key = { user . id } className = "hover:bg-[#35353b99]" >
407+ { cells }
408+ </ TableRow > ) ;
409+ } }
402410 </ TableBody >
403411 </ Table >
404412 </ div > ) ;
0 commit comments