diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-07 01:44:45 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-07 01:44:45 +0000 |
| commit | 90f79a7a691943a496f67f01c1e493256070e4de (patch) | |
| tree | 37275fde3ae08c2bca384fbbc8eb378de7e39230 /lib/login-session/table/login-sessions-table-columns.tsx | |
| parent | fbb3b7f05737f9571b04b0a8f4f15c0928de8545 (diff) | |
(대표님) 변경사항 20250707 10시 43분 - unstaged 변경사항 추가
Diffstat (limited to 'lib/login-session/table/login-sessions-table-columns.tsx')
| -rw-r--r-- | lib/login-session/table/login-sessions-table-columns.tsx | 243 |
1 files changed, 243 insertions, 0 deletions
diff --git a/lib/login-session/table/login-sessions-table-columns.tsx b/lib/login-session/table/login-sessions-table-columns.tsx new file mode 100644 index 00000000..e3d8bc2f --- /dev/null +++ b/lib/login-session/table/login-sessions-table-columns.tsx @@ -0,0 +1,243 @@ +"use client" + +import * as React from "react" +import { type DataTableRowAction } from "@/types/table" +import { type ColumnDef } from "@tanstack/react-table" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { Checkbox } from "@/components/ui/checkbox" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuShortcut, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip" + +import { formatDate} from "@/lib/utils" +import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header" +import { ExtendedLoginSession } from "../validation" +import { Eye, Shield, LogOut, Ellipsis } from "lucide-react" + +interface GetColumnsProps { + setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<ExtendedLoginSession> | null>> +} + +export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<ExtendedLoginSession>[] { + return [ + { + id: "select", + header: ({ table }) => ( + <Checkbox + checked={ + table.getIsAllPageRowsSelected() || + (table.getIsSomePageRowsSelected() && "indeterminate") + } + onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)} + aria-label="Select all" + className="translate-y-0.5" + /> + ), + cell: ({ row }) => ( + <Checkbox + checked={row.getIsSelected()} + onCheckedChange={(value) => row.toggleSelected(!!value)} + aria-label="Select row" + className="translate-y-0.5" + /> + ), + enableSorting: false, + enableHiding: false, + }, + { + id: "사용자 정보", + header: "사용자 정보", + columns: [ + { + accessorKey: "userEmail", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="이메일" /> + ), + cell: ({ row }) => ( + <div className="flex flex-col"> + <span className="font-medium">{row.getValue("userEmail")}</span> + <span className="text-xs text-muted-foreground"> + {row.original.userName} + </span> + </div> + ), + }, + ], + }, + { + id: "세션 정보", + header: "세션 정보", + columns: [ + { + accessorKey: "loginAt", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="로그인 시간" /> + ), + cell: ({ row }) => { + const date = row.getValue("loginAt") as Date + return ( + <Tooltip> + <TooltipTrigger> + <div className="text-sm"> + {formatDate(date, "KR")} + </div> + </TooltipTrigger> + <TooltipContent> + {formatDate(date)} + </TooltipContent> + </Tooltip> + ) + }, + }, + { + accessorKey: "logoutAt", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="로그아웃 시간" /> + ), + cell: ({ row }) => { + const date = row.getValue("logoutAt") as Date | null + if (!date) { + return <span className="text-muted-foreground">-</span> + } + return ( + <Tooltip> + <TooltipTrigger> + <div className="text-sm"> + {formatDate(date, "KR")} + </div> + </TooltipTrigger> + <TooltipContent> + {formatDate(date)} + </TooltipContent> + </Tooltip> + ) + }, + }, + { + accessorKey: "sessionDuration", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="세션 지속시간" /> + ), + cell: ({ row }) => { + const duration = row.getValue("sessionDuration") as number | null + if (!duration) { + return <span className="text-muted-foreground">-</span> + } + + const hours = Math.floor(duration / 60) + const minutes = Math.floor(duration % 60) + + if (hours > 0) { + return `${hours}시간 ${minutes}분` + } + return `${minutes}분` + }, + }, + ], + }, + { + id: "인증 및 보안", + header: "인증 및 보안", + columns: [ + { + accessorKey: "authMethod", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="인증 방식" /> + ), + cell: ({ row }) => { + const authMethod = row.getValue("authMethod") as string + const variants = { + otp: "default", + email: "secondary", + sgips: "outline", + saml: "destructive", + } as const + + return ( + <Badge variant={variants[authMethod as keyof typeof variants] || "default"}> + {authMethod.toUpperCase()} + </Badge> + ) + }, + }, + { + accessorKey: "ipAddress", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="IP 주소" /> + ), + cell: ({ row }) => ( + <code className="text-xs bg-muted px-2 py-1 rounded"> + {row.getValue("ipAddress")} + </code> + ), + }, + { + accessorKey: "isCurrentlyActive", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="상태" /> + ), + cell: ({ row }) => { + const isActive = row.getValue("isCurrentlyActive") as boolean + return ( + <Badge variant={isActive ? "default" : "secondary"}> + {isActive ? "활성" : "비활성"} + </Badge> + ) + }, + }, + ], + }, + { + id: "actions", + cell: function Cell({ row }) { + const session = row.original + + return ( + <DropdownMenu> + <DropdownMenuTrigger asChild> + <Button + aria-label="Open menu" + variant="ghost" + className="flex size-8 p-0 data-[state=open]:bg-muted" + > + <Ellipsis className="size-4" aria-hidden="true" /> + </Button> + </DropdownMenuTrigger> + <DropdownMenuContent align="end" className="w-40"> + <DropdownMenuItem + onSelect={() => setRowAction({ type: "view", row })} + > + <Eye className="mr-2 size-4" aria-hidden="true" /> + 상세 보기 + </DropdownMenuItem> + <DropdownMenuItem + onSelect={() => setRowAction({ type: "viewSecurity", row })} + > + <Shield className="mr-2 size-4" aria-hidden="true" /> + 보안 정보 + </DropdownMenuItem> + {session.isCurrentlyActive && ( + <DropdownMenuItem + onSelect={() => setRowAction({ type: "forceLogout", row })} + className="text-red-600" + > + <LogOut className="mr-2 size-4" aria-hidden="true" /> + 강제 로그아웃 + <DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut> + </DropdownMenuItem> + )} + </DropdownMenuContent> + </DropdownMenu> + ) + }, + enableSorting: false, + enableHiding: false, + }, + ] +}
\ No newline at end of file |
