- OWNER 권한 신설, 최고관리자가 자기 회사 소속 직원(USER) 계정 생성·수정·삭제 관리 - 회사 사용자 API(/v1/company/user/*), 회원관리 페이지·폼 - 로그인/프로필 흐름 정비, 무인증 계정생성(/auth/create) 제거 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
97 lines
3.1 KiB
TypeScript
97 lines
3.1 KiB
TypeScript
import { Plus } from 'lucide-react';
|
|
import { useOverlayRouter } from '@/lib/useOverlayRouter';
|
|
import { showToast } from '@/lib/notify';
|
|
import { confirm } from '@/lib/confirm';
|
|
import { PageContainer } from '@/components/layout/PageContainer';
|
|
import { PageToolbar, SearchInput } from '@/components/layout/PageToolbar';
|
|
import { Button } from '@/components/ui/button';
|
|
import { useServerList } from '@/lib/useServerList';
|
|
import { useMembers } from '@/features/members/hooks/useMembers';
|
|
import { MemberTable } from '@/features/members/components/MemberTable';
|
|
import { MemberFormSheet } from '@/features/members/components/MemberFormSheet';
|
|
import type { Member } from '@/features/members/types';
|
|
import type { ListCompanyUsersParams } from '@/features/members/api';
|
|
|
|
export default function MembersPage() {
|
|
const list = useServerList({ pageSize: 10 });
|
|
const params: ListCompanyUsersParams = {
|
|
search: list.debouncedSearch || undefined,
|
|
page: list.page,
|
|
size: list.pageSize,
|
|
};
|
|
|
|
const { members, total, createMember, updateMember, deleteMember } = useMembers(params);
|
|
const totalPages = list.totalPages(total);
|
|
|
|
const overlay = useOverlayRouter(['new', 'detail']);
|
|
const editId = overlay.get('detail');
|
|
const editing = editId ? members.find((m) => m.user_id === editId) ?? null : null;
|
|
const formMode: 'create' | 'edit' = editId ? 'edit' : 'create';
|
|
const isFormOpen = overlay.has('new') || !!editing;
|
|
|
|
const openCreate = () => overlay.open('new');
|
|
const openEdit = (member: Member) => overlay.open('detail', member.user_id);
|
|
|
|
const handleDelete = async (userId: string, label: string) => {
|
|
if (
|
|
await confirm({
|
|
title: '계정 삭제',
|
|
description: `[${label}] 계정을 삭제하시겠습니까?`,
|
|
confirmText: '삭제',
|
|
destructive: true,
|
|
})
|
|
) {
|
|
try {
|
|
await deleteMember(userId);
|
|
showToast('계정이 삭제되었습니다.', 'info');
|
|
} catch (err) {
|
|
showToast(err instanceof Error ? err.message : '계정 삭제 실패', 'error');
|
|
}
|
|
}
|
|
};
|
|
|
|
return (
|
|
<PageContainer>
|
|
<PageToolbar
|
|
actions={
|
|
<Button onClick={openCreate}>
|
|
<Plus />
|
|
신규 계정 생성
|
|
</Button>
|
|
}
|
|
>
|
|
<SearchInput
|
|
id="member-search"
|
|
value={list.search}
|
|
onChange={(e) => list.setSearch(e.target.value)}
|
|
onKeyDown={(e) => e.key === 'Enter' && list.submitSearch()}
|
|
placeholder="로그인 ID, 이름 또는 이메일로 검색..."
|
|
/>
|
|
</PageToolbar>
|
|
|
|
<MemberTable
|
|
data={members}
|
|
onRowClick={openEdit}
|
|
page={list.page}
|
|
totalPages={totalPages}
|
|
totalCount={total}
|
|
pageSize={list.pageSize}
|
|
onPageChange={list.setPage}
|
|
/>
|
|
|
|
{isFormOpen && (
|
|
<MemberFormSheet
|
|
key={`${formMode}-${editing?.user_id ?? 'new'}`}
|
|
open
|
|
mode={formMode}
|
|
member={editing}
|
|
onCreate={createMember}
|
|
onUpdate={updateMember}
|
|
onDelete={handleDelete}
|
|
onClose={overlay.close}
|
|
/>
|
|
)}
|
|
</PageContainer>
|
|
);
|
|
}
|