From 30e5071cbaa30eba969b98345da76f0e82908df5 Mon Sep 17 00:00:00 2001 From: zenn Date: Thu, 23 Jul 2026 17:05:54 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B4=80=EB=A6=AC=EC=9E=90=20=EB=AF=B8?= =?UTF-8?q?=EB=94=94=EC=96=B4=20=ED=95=84=ED=84=B0=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=EA=B3=B5=ED=86=B5=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/admin/AdminFilterPillGroup.vue | 58 ++++++++++++++ components/admin/AdminSelectInput.vue | 15 +++- docs/map.md | 3 +- docs/todo.md | 2 +- docs/update.md | 6 ++ package-lock.json | 4 +- package.json | 2 +- pages/admin/media/index.vue | 92 ++++++++++------------- 8 files changed, 121 insertions(+), 61 deletions(-) create mode 100644 components/admin/AdminFilterPillGroup.vue diff --git a/components/admin/AdminFilterPillGroup.vue b/components/admin/AdminFilterPillGroup.vue new file mode 100644 index 0000000..f2d0de7 --- /dev/null +++ b/components/admin/AdminFilterPillGroup.vue @@ -0,0 +1,58 @@ + + + diff --git a/components/admin/AdminSelectInput.vue b/components/admin/AdminSelectInput.vue index c5bee96..e78180f 100644 --- a/components/admin/AdminSelectInput.vue +++ b/components/admin/AdminSelectInput.vue @@ -2,7 +2,7 @@ const props = defineProps({ /** 선택 입력 값 */ modelValue: { - type: String, + type: [String, Number], default: '' }, /** 선택 옵션 목록 */ @@ -34,13 +34,22 @@ const props = defineProps({ const emit = defineEmits(['update:modelValue']) +/** + * option에 전달할 실제 값을 반환한다. + * @param {Object} option - 선택 옵션 + * @returns {string|number} 선택 값 + */ +const getOptionValue = (option) => option.value + /** * 선택 값을 상위 컴포넌트에 전달한다. * @param {Event} event - 변경 이벤트 * @returns {void} */ const updateValue = (event) => { - emit('update:modelValue', event.target?.value || '') + const selectedValue = event.target?.value || '' + const selectedOption = props.options.find((option) => String(option.value) === selectedValue) + emit('update:modelValue', selectedOption ? selectedOption.value : selectedValue) } @@ -55,7 +64,7 @@ const updateValue = (event) => { :aria-label="label" @change="updateValue" > - diff --git a/docs/map.md b/docs/map.md index 451bd62..0addff4 100644 --- a/docs/map.md +++ b/docs/map.md @@ -189,7 +189,8 @@ | components/admin/AdminConfirmModal.vue | 관리자 공통 확인 모달(제목·본문 슬롯·확인/취소 버튼, primary/danger 톤) | | components/admin/AdminToast.vue | 관리자 공통 우측 상단 토스트 표시(성공·오류·안내 톤, 화면별 상단 위치 조정) | | components/admin/AdminSearchInput.vue | 관리자 공통 검색 입력(돋보기 아이콘, IME 검색 이벤트 전달, 외부 focus/select 노출) | -| components/admin/AdminSelectInput.vue | 관리자 공통 select 입력(기본 필터·정렬 옵션, chevron 아이콘, `v-model` 전달) | +| components/admin/AdminSelectInput.vue | 관리자 공통 select 입력(문자·숫자 옵션 값, 기본 필터·정렬 옵션, chevron 아이콘, `v-model` 전달) | +| components/admin/AdminFilterPillGroup.vue | 관리자 공통 pill 필터 그룹(선택 상태·개수 배지·aria-pressed) | | components/admin/AdminPagination.vue | 관리자 공통 번호형 페이지네이션(이전·다음 화살표, 첫/끝/현재 주변 페이지, ellipsis 접기) | | components/admin/AdminAnalyticsTrendChart.vue | 관리자 통계 추이 막대 차트(축 라벨·증감 툴팁·기간 페이지 이동) | | lib/analytics-chart-ui.js | 통계 차트 라벨·페이지네이션·증감 계산 유틸 | diff --git a/docs/todo.md b/docs/todo.md index 02582fb..81db33e 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -10,7 +10,7 @@ - `pages/admin/media/index.vue`: 미디어 목록, 폴더 트리, 상세 모달, 재생성 액션을 작은 단위로 분리. - 관리자 UI 공통 컴포넌트 정리. - 모달 패널, 필터 팝오버의 radius·shadow·z-index·색상 규칙을 공통화. - - 게시글/페이지/미디어 목록의 고급 필터 레이아웃을 같은 패턴으로 유지. + - 멤버 고급 필터와 설정 화면 select 입력의 공통 컴포넌트 적용 범위 검토. - 최근 브라우저 회귀 테스트 추가. - 태그 추천 선택 후 다음 태그 후보가 포커스 해제 없이 유지되는지 확인. - 관리자 미디어 대표 이미지/본문 이미지 탭 전환 중 역할 배지가 노출되지 않는지 확인. diff --git a/docs/update.md b/docs/update.md index dca7878..917d88d 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,11 @@ # 업데이트 이력 +## v1.11.9 + +- 관리자 공용 select 입력이 숫자 옵션 값을 보존하도록 보완. +- 관리자 미디어 표시 개수 select를 공용 `AdminSelectInput`으로 교체. +- 관리자 미디어 종류·사용 상태 필터를 공용 `AdminFilterPillGroup` 컴포넌트로 분리. + ## v1.11.8 - 관리자 목록 select 입력을 공용 `AdminSelectInput` 컴포넌트로 분리. diff --git a/package-lock.json b/package-lock.json index ab6b7c2..454d917 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sori.studio", - "version": "1.11.8", + "version": "1.11.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sori.studio", - "version": "1.11.8", + "version": "1.11.9", "hasInstallScript": true, "dependencies": { "@nuxtjs/tailwindcss": "^6.14.0", diff --git a/package.json b/package.json index c76b5b3..2d407ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sori.studio", - "version": "1.11.8", + "version": "1.11.9", "private": true, "type": "module", "imports": { diff --git a/pages/admin/media/index.vue b/pages/admin/media/index.vue index fe4c1ea..8d57d83 100644 --- a/pages/admin/media/index.vue +++ b/pages/admin/media/index.vue @@ -23,7 +23,10 @@ definePageMeta({ const MEDIA_THUMBNAIL_ROOT = '썸네일' /** 관리자 미디어 한 페이지 표시 개수 옵션 */ -const mediaPageSizeOptions = [50, 100, 200] +const mediaPageSizeOptions = [50, 100, 200].map((size) => ({ + value: size, + label: `${size}개` +})) /** 회원 등급 코드 → 미디어 배지 라벨 */ const memberRoleBadgeLabels = { @@ -489,11 +492,11 @@ const mediaKindFilterOptions = computed(() => { const countByKind = mediaCounts.value.kinds || {} return [ - { id: 'all', label: '전체', count: countByKind.all || 0 }, - { id: 'image', label: '이미지', count: countByKind.image || 0 }, - { id: 'video', label: '영상', count: countByKind.video || 0 }, - { id: 'audio', label: '음악', count: countByKind.audio || 0 }, - { id: 'file', label: '파일', count: countByKind.file || 0 } + { value: 'all', label: '전체', count: countByKind.all || 0 }, + { value: 'image', label: '이미지', count: countByKind.image || 0 }, + { value: 'video', label: '영상', count: countByKind.video || 0 }, + { value: 'audio', label: '음악', count: countByKind.audio || 0 }, + { value: 'file', label: '파일', count: countByKind.file || 0 } ] }) @@ -501,8 +504,8 @@ const mediaUsageFilterOptions = computed(() => { const usageCounts = mediaCounts.value.usage || {} return [ - { id: 'all', label: '전체', count: usageCounts.all || 0, unusedOnly: false }, - { id: 'unused', label: '미사용', count: usageCounts.unused || 0, unusedOnly: true } + { value: false, id: 'all', label: '전체', count: usageCounts.all || 0 }, + { value: true, id: 'unused', label: '미사용', count: usageCounts.unused || 0 } ] }) @@ -676,11 +679,11 @@ const setMediaPage = (page) => { /** * 관리자 미디어 페이지 표시 개수를 변경한다. - * @param {Event} event - select 변경 이벤트 + * @param {number} pageSize - 변경할 표시 개수 * @returns {void} */ -const setMediaPageSize = (event) => { - mediaPageSize.value = Number(event.target.value) || 50 +const setMediaPageSize = (pageSize) => { + mediaPageSize.value = Number(pageSize) || 50 mediaPage.value = 1 clearMediaSelection() closeMediaDetail() @@ -1411,31 +1414,21 @@ watch(() => mediaPagination.value.page, (page) => {
- - + +
@@ -1536,24 +1529,17 @@ watch(() => mediaPagination.value.page, (page) => {

-