diff --git a/docs/history.md b/docs/history.md
index 9d8217d..caa3309 100644
--- a/docs/history.md
+++ b/docs/history.md
@@ -1,5 +1,11 @@
# 의사결정 이력
+## 2026-05-13 v0.0.109
+
+### 관리자 사이드바 하단 사용자 영역 정리
+
+상단 메뉴 아래에 로그아웃이 바로 붙어 있으면 Ghost형 관리자 내비게이션의 정보 구조와 달라지고, 주요 메뉴와 세션 액션이 같은 레벨로 보인다. 로그아웃은 하단 사용자 썸네일 드롭다운으로 옮기고, 설정은 하단 아이콘으로 배치해 상단 메뉴는 콘텐츠 관리 항목 중심으로 유지한다. 멤버 항목에는 총 멤버 수를 함께 보여 관리자가 현재 규모를 즉시 확인할 수 있게 한다.
+
## 2026-05-13 v0.0.108
### 관리자 캔버스 높이와 사이드바 폭 정리
diff --git a/docs/map.md b/docs/map.md
index e6feda8..fa5c5bc 100644
--- a/docs/map.md
+++ b/docs/map.md
@@ -8,7 +8,7 @@
|------|------|
| layouts/default.vue | 메인·목록·태그 — 3열 `gap`+중앙 `1fr`, `site-main` `max-w-[720px]`, 모바일 슬라이드 메뉴 |
| layouts/post.vue | 개별 게시물 — `default`와 동일 |
-| layouts/admin.vue | 관리자 전체, 320px 밝은 Ghost형 사이드바, 우측 전체 높이 캔버스, 게시글 `+` 새 글 진입, 글 작성/수정 화면의 전체 화면 편집 모드와 문서 스크롤 잠금 |
+| layouts/admin.vue | 관리자 전체, 320px 밝은 Ghost형 사이드바, 우측 전체 높이 캔버스, 멤버 수 표시, 하단 사용자 드롭다운·설정, 게시글 `+` 새 글 진입, 글 작성/수정 화면의 전체 화면 편집 모드와 문서 스크롤 잠금 |
| layouts/page.vue | 고정 페이지 전체 화면 |
## Composables
diff --git a/docs/spec.md b/docs/spec.md
index 5e2dbb6..533e08c 100644
--- a/docs/spec.md
+++ b/docs/spec.md
@@ -117,7 +117,10 @@ layouts/
- 관리자 사이드바는 데스크톱에서 320px 너비를 사용한다.
- 관리자 우측 캔버스는 기본 `min-h-screen`과 `bg-paper`를 유지해 콘텐츠 높이가 짧아도 배경이 화면 전체를 채운다. 일반 관리자 화면은 레이아웃 레벨에서 넉넉한 외부 여백을 둔다.
- 게시글 메뉴 라벨은 `게시글`로 표시하고, 우측 `+` 아이콘은 `/admin/posts/new`로 바로 이동한다.
-- 게시글·페이지·태그·미디어·멤버·설정 메뉴는 전용 SVG 아이콘을 사용한다.
+- 메뉴 관리 항목은 `네비게이션`으로 표시한다.
+- 게시글·페이지·태그·미디어·네비게이션·멤버·설정 메뉴는 전용 SVG 아이콘을 사용한다.
+- 멤버 메뉴는 우측에 현재 총 멤버 수를 표시한다.
+- 로그아웃은 사이드바 상단 메뉴가 아니라 하단 사용자 썸네일 드롭다운 안에서 제공한다. 하단에는 사용자 썸네일 트리거와 설정 아이콘을 둔다.
---
diff --git a/docs/update.md b/docs/update.md
index ed24b2f..bef07e9 100644
--- a/docs/update.md
+++ b/docs/update.md
@@ -1,5 +1,14 @@
# 업데이트 이력
+## v0.0.109
+
+- 관리자 사이드바 `메뉴` 항목을 `네비게이션`으로 변경하고 전용 아이콘 적용.
+- 관리자 게시글 아이콘을 Ghost형 편집 아이콘으로 교체.
+- 관리자 멤버 메뉴 우측에 총 멤버 수 표시 추가.
+- 관리자 로그아웃을 상단 메뉴에서 제거하고 하단 사용자 드롭다운으로 이동.
+- 관리자 하단에 사용자 썸네일 트리거와 설정 아이콘 추가.
+- 패키지 버전 `0.0.109`로 갱신.
+
## v0.0.108
- 관리자 사이드바 너비를 Ghost 기준 320px로 조정.
diff --git a/layouts/admin.vue b/layouts/admin.vue
index 2c62e1c..3174a8b 100644
--- a/layouts/admin.vue
+++ b/layouts/admin.vue
@@ -5,6 +5,25 @@ const isPostEditorRoute = computed(() => route.path === '/admin/posts/new'
|| (route.path.startsWith('/admin/posts/') && route.path !== '/admin/posts/preview'))
const editorDocumentClass = 'admin-post-editor-document'
+const adminUserMenuOpen = ref(false)
+
+const { data: adminMember } = await useFetch('/api/auth/me', {
+ default: () => ({
+ username: '',
+ email: '',
+ avatarUrl: ''
+ })
+})
+
+const { data: adminMembers } = await useFetch('/admin/api/members', {
+ default: () => []
+})
+
+const memberCount = computed(() => adminMembers.value.length)
+const adminDisplayName = computed(() => adminMember.value?.username || adminMember.value?.email || '관리자')
+const adminDisplayEmail = computed(() => adminMember.value?.email || '')
+const adminAvatarUrl = computed(() => adminMember.value?.avatarUrl || '')
+const adminAvatarInitial = computed(() => adminDisplayName.value.slice(0, 1).toUpperCase() || 'A')
/**
* 관리자 내비게이션 활성 경로 확인
@@ -13,6 +32,39 @@ const editorDocumentClass = 'admin-post-editor-document'
*/
const isAdminNavActive = (path) => route.path === path || route.path.startsWith(`${path}/`)
+/**
+ * 관리자 사용자 메뉴 닫기
+ * @returns {void}
+ */
+const closeAdminUserMenu = () => {
+ adminUserMenuOpen.value = false
+}
+
+/**
+ * 관리자 사용자 메뉴 토글
+ * @returns {void}
+ */
+const toggleAdminUserMenu = () => {
+ adminUserMenuOpen.value = !adminUserMenuOpen.value
+}
+
+/**
+ * 외부 클릭 시 관리자 사용자 메뉴 닫기
+ * @param {PointerEvent} event - 포인터 이벤트
+ * @returns {void}
+ */
+const onAdminDocumentPointerDown = (event) => {
+ if (!adminUserMenuOpen.value || !(event.target instanceof HTMLElement)) {
+ return
+ }
+
+ if (event.target.closest('[data-admin-user-menu]')) {
+ return
+ }
+
+ closeAdminUserMenu()
+}
+
/**
* 글쓰기 전체 화면 문서 스크롤 잠금 적용
* @returns {void}
@@ -28,6 +80,10 @@ const syncPostEditorDocumentClass = () => {
watchEffect(syncPostEditorDocumentClass)
+onMounted(() => {
+ document.addEventListener('pointerdown', onAdminDocumentPointerDown)
+})
+
onBeforeUnmount(() => {
if (!import.meta.client) {
return
@@ -35,6 +91,7 @@ onBeforeUnmount(() => {
document.documentElement.classList.remove(editorDocumentClass)
document.body.classList.remove(editorDocumentClass)
+ document.removeEventListener('pointerdown', onAdminDocumentPointerDown)
})
/**
@@ -45,6 +102,7 @@ const logoutAdmin = async () => {
await $fetch('/admin/api/auth/logout', {
method: 'POST'
})
+ closeAdminUserMenu()
await navigateTo('/admin/login')
}
@@ -56,7 +114,7 @@ const logoutAdmin = async () => {
>