add avatar user list
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-07-09 11:20:17 +09:00
parent c06acdda8d
commit e0cc88ad45
2 changed files with 46 additions and 13 deletions
+45 -13
View File
@@ -3,6 +3,7 @@ import { useEffect, useMemo, useState } from 'react';
import { DataTableCard, type DataTableColumn, type DataTableFilter } from '../../components/data-table';
import { Button, Modal } from '../../components/ui';
import { PageTitle } from '../../layout/AppLayout';
import { cn } from '../../lib/utils';
import { loadUserSessions, lockUser, terminateUserSession, unlockUser, type UserSession } from './operations-api';
import { formatDateTime, LoadingModal, StatusBadge, useMinimumLoading } from './operation-utils';
@@ -56,11 +57,14 @@ export function UserSessionsPage() {
{
key: 'user',
header: '사용자',
className: 'w-[240px] px-5 py-3 font-medium',
className: 'w-[280px] px-5 py-3 font-medium',
cell: (row) => (
<div className="min-w-0">
<p className="truncate font-semibold">{row.user}</p>
{row.email ? <p className="mt-1 truncate text-xs font-medium text-muted-foreground">{row.email}</p> : null}
<div className="flex min-w-0 items-center gap-3">
<UserAvatar session={row} />
<div className="min-w-0">
<p className="truncate font-semibold">{row.user}</p>
{row.email ? <p className="mt-1 truncate text-xs font-medium text-muted-foreground">{row.email}</p> : null}
</div>
</div>
)
},
@@ -174,13 +178,16 @@ export function UserSessionsPage() {
{detailSession ? (
<div className="space-y-5">
<div className="flex min-w-0 items-start justify-between gap-4 rounded-md border border-border bg-muted/20 px-4 py-3">
<div className="min-w-0">
<p className="truncate text-sm font-semibold text-foreground">{detailSession.user}</p>
<p className="mt-1 truncate text-xs text-muted-foreground">{detailSession.email ?? '-'}</p>
<div className="flex min-w-0 items-center gap-3">
<UserAvatar session={detailSession} size="lg" />
<div className="min-w-0">
<p className="truncate text-sm font-semibold text-foreground">{detailSession.user}</p>
<p className="mt-1 truncate text-xs text-muted-foreground">{detailSession.email ?? '-'}</p>
</div>
</div>
<StatusBadge status={detailSession.state} />
</div>
<div className="grid gap-3 text-sm sm:grid-cols-2">
<div className="grid gap-3 text-sm">
<DetailItem label="사용자 ID" value={detailSession.userId} mono />
<DetailItem label="조직" value={detailSession.primaryAffiliation?.organizationName ?? '-'} />
<DetailItem label="조직 유형" value={organizationTypeLabel(detailSession.primaryAffiliation?.organizationType)} />
@@ -212,11 +219,36 @@ function organizationTypeLabel(type?: string | null) {
return type ? labels[type] ?? type : '-';
}
function DetailItem({ label, value, mono = false }: { label: string; value: string; mono?: boolean }) {
function UserAvatar({ session, size = 'sm' }: { session: UserSession; size?: 'sm' | 'lg' }) {
const [failed, setFailed] = useState(false);
const initial = (session.user || session.email || '?').trim().charAt(0).toUpperCase() || '?';
const sizeClassName = size === 'lg' ? 'size-14 text-lg' : 'size-9 text-sm';
if (session.avatarUrl && !failed) {
return (
<img
src={session.avatarUrl}
alt=""
className={cn(sizeClassName, 'shrink-0 rounded-full border border-border bg-muted object-cover')}
onError={() => setFailed(true)}
/>
);
}
return (
<div className="min-w-0 rounded-md border border-border bg-background px-3 py-2">
<p className="text-xs font-medium text-muted-foreground">{label}</p>
<p className={`mt-1 truncate text-sm text-foreground ${mono ? 'font-mono text-xs' : 'font-medium'}`}>{value || '-'}</p>
</div>
<span className={cn(sizeClassName, 'inline-flex shrink-0 items-center justify-center rounded-full border border-border bg-muted font-semibold text-muted-foreground')}>
{initial}
</span>
);
}
function DetailItem({ label, value, mono = false }: { label: string; value: string; mono?: boolean }) {
return (
<label className="block min-w-0">
<span className="text-xs font-medium text-muted-foreground">{label}</span>
<div className={cn('mt-2 min-h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm font-medium text-foreground', mono && 'break-all font-mono text-xs font-normal')}>
{value || '-'}
</div>
</label>
);
}