import { MoreHorizontal } from 'lucide-react'; import { useEffect, useMemo, useState } from 'react'; import { DataTableCard, type DataTableColumn, type DataTableFilter } from '../../components/data-table'; import { Button } from '../../components/ui'; import { PageTitle } from '../../layout/AppLayout'; import { loadUserSessions, lockUser, terminateUserSession, unlockUser, type UserSession } from './operations-api'; import { formatDateTime, LoadingModal, StatusBadge, useMinimumLoading } from './operation-utils'; export function UserSessionsPage() { const [sessions, setSessions] = useState([]); const [loading, setLoading] = useState(true); const [failed, setFailed] = useState(false); const [menuSession, setMenuSession] = useState<{ id: string; x: number; y: number } | null>(null); const loadingVisible = useMinimumLoading(loading); const refresh = () => { setLoading(true); loadUserSessions() .then((nextSessions) => { setSessions(nextSessions); setFailed(false); }) .catch(() => { setSessions([]); setFailed(true); }) .finally(() => setLoading(false)); }; useEffect(() => { refresh(); }, []); useEffect(() => { if (!menuSession) return; const close = () => setMenuSession(null); const closeOnEscape = (event: KeyboardEvent) => { if (event.key === 'Escape') setMenuSession(null); }; window.addEventListener('click', close); window.addEventListener('keydown', closeOnEscape); return () => { window.removeEventListener('click', close); window.removeEventListener('keydown', closeOnEscape); }; }, [menuSession]); const rows = useMemo(() => sessions.map((session, index) => ({ ...session, no: sessions.length - index })), [sessions]); const columns = useMemo>>( () => [ { key: 'no', header: 'No.', className: 'w-20 px-5 py-3 font-medium', cell: (row) => {row.no} }, { key: 'user', header: '사용자', className: 'w-[240px] px-5 py-3 font-medium', cell: (row) => (

{row.user}

{row.email ?

{row.email}

: null}
) }, { key: 'role', header: '권한', cell: (row) => {row.role} }, { key: 'session', header: '세션', cell: (row) => {row.session} }, { key: 'expiresAt', header: '만료', cell: (row) => {formatDateTime(row.expiresAt)} }, { key: 'lastSeen', header: '최근 접속', cell: (row) => {formatDateTime(row.lastSeen)} }, { key: 'state', header: '상태', cell: (row) => }, { key: 'actions', header: '', className: 'w-20 px-5 py-3 text-right font-medium', cell: (row) => (
) } ], [menuSession?.id] ); const filters = useMemo>>( () => [ { key: 'state', label: '상태', options: [...new Set(sessions.map((row) => row.state))].map((state) => ({ label: state, value: state })), getValue: (row) => row.state } ], [sessions] ); return ( <> {failed ? (
사용자/세션 데이터를 불러오지 못했습니다.
) : null} `${row.user} ${row.email ?? ''} ${row.role} ${row.session} ${row.state}`} getRowKey={(row) => row.id} pageSize={6} minWidth={1180} loading={loading} emptyMessage="표시할 사용자/세션 데이터가 없습니다." /> {menuSession ? (() => { const row = rows.find((item) => item.id === menuSession.id); if (!row) return null; const locked = row.state === '잠김'; return (
event.stopPropagation()} className="fixed z-[70] min-w-32 overflow-hidden rounded-md border border-border bg-card py-1 text-left shadow-xl" style={{ left: menuSession.x, top: menuSession.y, transform: 'translateX(-100%)' }}>
); })() : null} ); }