File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
'use client';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { FoxBadge, type FoxBadgeColor } from '@fox/core/components/fox-badge';
import { FoxButton } from '@fox/core/components/fox-button';
import { FoxButtonGroup } from '@fox/core/components/fox-button-group';
import {
FoxListContainer,
type FoxListColumn,
} from '@fox/core/components/fox-list-container';
import { FoxSelectText } from '@fox/core/components/fox-select-text';
import { FoxStatusIndicator } from '@fox/core/components/fox-status-indicator';
import {
formatAdminLockStatusLabel,
formatAdminRoleLabel,
formatOptionalValue,
type AdminMember,
} from '@/lib/domain/admin-member';
import {
ADMIN_MEMBERS_EXCEL_PATH,
ADMIN_MEMBER_PAGE_SIZE_OPTIONS,
ADMIN_MEMBER_SEARCH_FIELD_OPTIONS,
ADMIN_MEMBER_SORT_OPTIONS,
buildAdminMemberHref,
type AdminMemberPageSize,
type AdminMemberQuery,
type AdminMemberSearchField,
type AdminMemberSortOption,
} from '@/lib/domain/admin-member-query';
import { AdminMemberCreateModal } from './admin-member-create-modal';
import { AdminMemberRowActions } from './admin-member-row-actions';
interface AdminMemberListProps {
items: AdminMember[];
query: AdminMemberQuery;
currentPage: number;
totalPages: number;
totalCount: number;
currentAdminId: string;
}
const FILL_COLUMN_WIDTH = 277;
const BADGE_COLOR_BY_ROLE: Record<string, FoxBadgeColor> = {
ROLE_SYSTEM: 'accent',
ROLE_ADMIN: 'primary',
};
export function AdminMemberList({
items,
query,
currentPage,
totalPages,
totalCount,
currentAdminId,
}: AdminMemberListProps) {
const router = useRouter();
const [isCreateOpen, setIsCreateOpen] = useState(false);
function go(patch: Partial<AdminMemberQuery>) {
router.replace(buildAdminMemberHref(query, { ...patch, page: 1 }));
}
const offset = (currentPage - 1) * query.pageSize;
const columns: FoxListColumn<AdminMember>[] = [
{
key: 'no',
header: '번호',
width: 80,
render: (_row, index) => totalCount - offset - index,
},
{ key: 'name', header: '이름', width: FILL_COLUMN_WIDTH },
{ key: 'loginId', header: '아이디', width: FILL_COLUMN_WIDTH },
{
key: 'phoneNumber',
header: '휴대전화번호',
width: 200,
render: (row) => formatOptionalValue(row.phoneNumber),
},
{
key: 'email',
header: '이메일',
align: 'start',
width: FILL_COLUMN_WIDTH,
render: (row) => formatOptionalValue(row.email),
},
{
key: 'role',
header: '역할',
width: 120,
render: (row) => (
<FoxBadge
type="pastel"
color={BADGE_COLOR_BY_ROLE[row.roleCode] ?? 'neutral'}
size="md"
>
{formatAdminRoleLabel(row.roleCode)}
</FoxBadge>
),
},
{
key: 'isLocked',
header: '잠김여부',
width: 120,
render: (row) =>
row.isLocked === null ? (
formatAdminLockStatusLabel(row.isLocked)
) : (
<FoxStatusIndicator
type={row.isLocked ? 'danger' : 'success'}
label={formatAdminLockStatusLabel(row.isLocked)}
/>
),
},
{
key: 'createdAt',
header: '생성일',
width: 160,
render: (row) => formatOptionalValue(row.createdAt),
},
{
key: 'actions',
header: '관리',
width: 160,
render: (row) => (
<AdminMemberRowActions
member={row}
isSelf={row.id === currentAdminId}
/>
),
},
];
return (
<>
<FoxListContainer<AdminMember>
title="관리자 회원 목록"
breadcrumb={[
{ label: '홈', href: '/' },
{ label: '회원관리' },
{ label: '관리자 회원' },
]}
caption="관리자 회원 목록"
columns={columns}
rows={items}
rowKey={(row) => row.id}
totalCount={totalCount}
emptyMessage="검색 결과가 없습니다. 다른 검색어로 다시 시도해 주세요."
sorts={
<>
<FoxSelectText
size="sm"
ariaLabel="정렬"
icon={<i className="fox-ico fox-ico-ArrowsDownUp" aria-hidden="true" />}
options={ADMIN_MEMBER_SORT_OPTIONS.map((option) => ({
value: option.value,
label: option.label,
}))}
value={query.sort}
onValueChange={(value) => go({ sort: value as AdminMemberSortOption })}
/>
<FoxSelectText
size="sm"
ariaLabel="페이지 크기"
options={ADMIN_MEMBER_PAGE_SIZE_OPTIONS.map((size) => ({
value: String(size),
label: `${size}개씩 보기`,
}))}
value={String(query.pageSize)}
onValueChange={(value) =>
go({ pageSize: Number(value) as AdminMemberPageSize })
}
/>
</>
}
searchFields={ADMIN_MEMBER_SEARCH_FIELD_OPTIONS.map((option) => ({
value: option.value,
label: option.label,
}))}
searchField={query.searchField}
keyword={query.keyword}
toolbarActions={
<FoxButtonGroup size="md">
<form action={ADMIN_MEMBERS_EXCEL_PATH} method="get">
<FoxButton
type="secondary"
size="md"
htmlType="submit"
leadingIcon={<i className="fox-ico fox-ico-DownloadSimple" aria-hidden="true" />}
label="엑셀 다운로드"
/>
</form>
<FoxButton
type="primary"
size="md"
leadingIcon={<i className="fox-ico fox-ico-Plus" aria-hidden="true" />}
label="신규 등록"
onAction={() => setIsCreateOpen(true)}
/>
</FoxButtonGroup>
}
page={currentPage}
totalPages={totalPages}
buildHref={(patch) =>
buildAdminMemberHref(query, { page: Number(patch.page ?? 1) })
}
onQueryChange={(patch) => {
if (patch.keyword !== undefined || patch.searchField !== undefined) {
go({
keyword: String(patch.keyword ?? ''),
searchField: (patch.searchField ??
query.searchField) as AdminMemberSearchField,
});
}
}}
/>
{isCreateOpen && (
<AdminMemberCreateModal onClose={() => setIsCreateOpen(false)} />
)}
</>
);
}