+
+
+ 가입
+
+ {{ formatRelativeTime(member?.createdAt) }}
+
+
+
+
+
+
+
diff --git a/db/migrations/020_add_member_admin_fields.sql b/db/migrations/020_add_member_admin_fields.sql
new file mode 100644
index 0000000..a104e47
--- /dev/null
+++ b/db/migrations/020_add_member_admin_fields.sql
@@ -0,0 +1,5 @@
+ALTER TABLE users
+ ADD COLUMN IF NOT EXISTS member_labels TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[];
+
+ALTER TABLE users
+ ADD COLUMN IF NOT EXISTS member_note TEXT NOT NULL DEFAULT '';
diff --git a/docs/history.md b/docs/history.md
index 39f38af..33a43af 100644
--- a/docs/history.md
+++ b/docs/history.md
@@ -1,5 +1,11 @@
# 의사결정 이력
+## 2026-05-13 v0.0.111
+
+### 관리자 멤버 상세와 추가 화면 분리
+
+멤버 목록에서 모든 편집 기능을 처리하면 목록의 스캔성이 떨어지고 권한 변경 같은 민감 액션도 너무 쉽게 노출된다. 목록은 관측과 진입에 집중하고, 개별 회원의 이름·이메일·레이블·관리자 노트는 별도 상세 화면에서 저장한다. 레이블은 아직 공개 기능에 쓰지 않지만 이후 사용자별 칭호나 분류로 확장할 수 있도록 배열 컬럼으로 두고, 신규 회원은 활동 이력이 없으므로 활동 섹션을 렌더링하지 않는다.
+
## 2026-05-13 v0.0.110
### 관리자 멤버 목록 정보 밀도 정리
diff --git a/docs/map.md b/docs/map.md
index 01c5de2..ac249c7 100644
--- a/docs/map.md
+++ b/docs/map.md
@@ -60,6 +60,7 @@
| components/admin/AdminBlockEditor.vue | 관리자 글 블록형 에디터, 이미지/갤러리/콜아웃/토글/임베드 블록, 한글 조합 입력 처리, Shift+Enter 줄바꿈, 코드 블록 단축 변환, AFFiNE 참고 세로 막대형 블록 핸들 선택/삭제/드래그 이동과 삽입선 표시, 하단 빈 입력 블록 유지, 본문 placeholder 표시 |
| components/admin/AdminNavPrimaryBranch.vue | 관리자 상단 네비 트리(테이블·태그와 동일한 행 드래그 하이라이트, 하위·삭제) |
| components/admin/AdminTagForm.vue | 관리자 태그 생성/수정 폼(이름/슬러그/설명/색상만 편집) |
+| components/admin/AdminMemberForm.vue | 관리자 멤버 추가/수정 폼(Ghost형 2열, 기본 정보·레이블·관리자 노트·활동 요약) |
## 콘텐츠 컴포넌트
@@ -103,6 +104,8 @@
| pages/admin/tags/[id].vue | 태그 수정 |
| pages/admin/settings/index.vue | 사이트 설정 + 관리자 프로필(썸네일/이름) + 관리자 비밀번호 변경 |
| pages/admin/members/index.vue | 관리자 멤버 목록(Ghost형 테이블, 검색, 멤버 추가 버튼, 닉네임+이메일, 가입일+최근 활동, IP, 댓글 수, 활동 상태) |
+| pages/admin/members/new.vue | 관리자 멤버 추가(썸네일 URL, 이름, 이메일, 레이블, 관리자 노트) |
+| pages/admin/members/[id].vue | 관리자 멤버 상세/수정(회원 요약, 가입 정보, 활동 요약, 기본 정보 저장) |
## 공개 페이지
@@ -183,6 +186,9 @@
| server/routes/admin/api/navigation.get.js | 관리자 네비게이션 목록 API |
| server/routes/admin/api/navigation.put.js | 관리자 네비게이션 일괄 저장 API |
| server/routes/admin/api/members.get.js | 관리자 멤버 목록 API |
+| server/routes/admin/api/members.post.js | 관리자 멤버 생성 API |
+| server/routes/admin/api/members/[id].get.js | 관리자 멤버 상세 API |
+| server/routes/admin/api/members/[id].put.js | 관리자 멤버 기본 정보 수정 API |
| server/routes/admin/api/members/[id]/role.put.js | 관리자 멤버 권한 변경 API |
| server/utils/content-schema.js | Zod 콘텐츠 스키마 |
| server/utils/sample-content.js | 샘플 콘텐츠 저장소 |
diff --git a/docs/spec.md b/docs/spec.md
index 3b1abe6..4fc5a84 100644
--- a/docs/spec.md
+++ b/docs/spec.md
@@ -415,6 +415,9 @@ components/content/
- `GET /admin/api/navigation` - 네비게이션 항목 목록
- `PUT /admin/api/navigation` - 네비게이션 항목 일괄 저장
- `GET /admin/api/members` - 회원 목록(권한 코드, 최근 접속, 접속 IP, 댓글 수 포함)
+- `POST /admin/api/members` - 관리자 회원 생성. 본문: `username`, `email`, 선택 `avatarUrl`, `labels`, `note`. 생성된 회원은 `member` 권한이며 초기 비밀번호는 임의 해시로 저장한다.
+- `GET /admin/api/members/:id` - 관리자 회원 상세(썸네일, 이름, 이메일, 레이블, 관리자 노트, 활동 요약 포함)
+- `PUT /admin/api/members/:id` - 관리자 회원 기본 정보 수정. 본문: `username`, `email`, 선택 `avatarUrl`, `labels`, `note`
- `PUT /admin/api/members/:id/role` - 회원 권한 변경(`owner`/`admin`/`member`)
> 글 발행/초안/비공개 전환은 현재 `PUT /admin/api/posts/:id`의 `status` 값으로 처리한다.
@@ -551,8 +554,10 @@ components/content/
- 관리자 로그인 성공 시 관리자/회원 세션 쿠키를 함께 설정해 관리자 화면에서도 프로필(썸네일, 이름) 수정 API를 공통으로 사용한다.
- 관리자 설정 화면에서 관리자 프로필(이름, 썸네일)과 관리자 비밀번호를 수정할 수 있다.
- 관리자 멤버 목록은 Ghost형 테이블 기준으로 닉네임 아래 이메일, 가입일 아래 최근 활동을 함께 표시한다.
-- 관리자 멤버 목록은 멤버 검색과 멤버 추가 버튼을 제공한다. 멤버 추가 버튼의 실제 생성 화면 연결은 후속 작업에서 처리한다.
+- 관리자 멤버 목록은 멤버 검색과 멤버 추가 버튼을 제공한다.
- 관리자 멤버 목록은 뉴스레터 지표 대신 댓글 작성 개수를 표시한다.
+- 관리자 멤버 상세/추가 화면은 Ghost형 편집 화면을 기준으로 썸네일, 이름, 이메일, 레이블, 관리자 노트를 편집한다. 기존 회원 상세는 가입 정보와 활동 요약을 표시하고, 신규 회원 화면은 활동 영역을 표시하지 않는다.
+- `users.member_labels`는 관리자용 문자열 배열이며, 이후 사용자별 칭호/분류 용도로 확장한다. `users.member_note`는 관리자에게만 보이는 500자 이하 메모다.
- 관리자 멤버 권한은 `소유자(owner)`, `관리자(admin)`, `멤버(member)` 3단계를 유지하되, 목록 화면에서는 변경 UI를 노출하지 않고 상세/후속 화면에서 변경한다.
- 관리자 페이지 접근은 `/admin/api/auth/me` 확인 후 허용한다.
- 관리자 세션 토큰은 `ADMIN_PASSWORD` 기반 HMAC 서명으로 검증한다.
diff --git a/docs/todo.md b/docs/todo.md
index ed5ef02..ec6e944 100644
--- a/docs/todo.md
+++ b/docs/todo.md
@@ -7,6 +7,7 @@
## 2차 관리자 개발
- [ ] 미디어 라이브러리 프로필/사이트 설정 이미지 사용처 추적
+- [ ] 관리자 멤버 추가 후 초대 메일 또는 비밀번호 설정 안내 플로우 연결
## 프론트엔드 개발
diff --git a/docs/update.md b/docs/update.md
index a7dc987..9589185 100644
--- a/docs/update.md
+++ b/docs/update.md
@@ -1,5 +1,15 @@
# 업데이트 이력
+## v0.0.111
+
+- 관리자 멤버 상세 화면(`/admin/members/:id`) 추가.
+- 관리자 멤버 추가 화면(`/admin/members/new`) 추가.
+- 멤버 목록 행 클릭 시 상세 화면으로 이동하도록 수정.
+- 멤버 기본 정보 저장 API(`GET/PUT /admin/api/members/:id`, `POST /admin/api/members`) 추가.
+- 회원 레이블·관리자 노트 저장 컬럼 마이그레이션(`020_add_member_admin_fields.sql`) 추가.
+- 멤버 폼에 썸네일 URL, 이름, 이메일, 레이블, 관리자 노트, 기존 회원 활동 요약 표시.
+- 패키지 버전 `0.0.111`로 갱신.
+
## v0.0.110
- 관리자 멤버 목록을 Ghost형 테이블 구조로 재정리.
diff --git a/package-lock.json b/package-lock.json
index 08d5853..83d2b63 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "sori.studio",
- "version": "0.0.110",
+ "version": "0.0.111",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sori.studio",
- "version": "0.0.110",
+ "version": "0.0.111",
"hasInstallScript": true,
"dependencies": {
"@nuxtjs/tailwindcss": "^6.14.0",
diff --git a/package.json b/package.json
index c24251e..e5b7b28 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "sori.studio",
- "version": "0.0.110",
+ "version": "0.0.111",
"private": true,
"type": "module",
"imports": {
diff --git a/pages/admin/members/[id].vue b/pages/admin/members/[id].vue
new file mode 100644
index 0000000..97e33cb
--- /dev/null
+++ b/pages/admin/members/[id].vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+ {{ error?.data?.message || '회원 정보를 불러올 수 없습니다.' }}
+
+
+
diff --git a/pages/admin/members/index.vue b/pages/admin/members/index.vue
index 46fc391..cd0d346 100644
--- a/pages/admin/members/index.vue
+++ b/pages/admin/members/index.vue
@@ -29,6 +29,19 @@ const memberCountLabel = computed(() => {
return `${count}명`
})
+/**
+ * 회원 상세 화면으로 이동한다.
+ * @param {Object} member - 회원 정보
+ * @returns {Promise} 이동 처리
+ */
+const navigateToMember = async (member) => {
+ if (!member?.id) {
+ return
+ }
+
+ await navigateTo(`/admin/members/${member.id}`)
+}
+
/**
* 회원 이니셜을 반환한다.
* @param {Object} member - 회원 정보
@@ -100,9 +113,12 @@ const formatRelativeTime = (value) => {
-