diff --git a/docs/history.md b/docs/history.md index 113f911..8793148 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,5 +1,9 @@ # 의사결정 이력 +## 2026-04-02 v1.3.74 +- 관리자 공용 게임 선택 모달은 단순 검색만 제공하기보다, 현재 문맥에서 이미 선택 불가능한 대상을 `이미 추가됨`으로 명시하고 막아 주는 편이 운영 실수를 줄이는 데 더 효과적이라고 정리했다. +- 프로젝트 표기는 관리자 헤더 상단보다 사이드바 최하단의 작은 카피라이트 문구로 빼는 편이 정보 밀도를 덜 방해한다고 판단했다. + ## 2026-04-02 v1.3.73 - 게임 선택이 여러 관리자 화면에 퍼지기 시작한 시점에서는 일부 화면만 셀렉트나 내부 리스트를 유지하기보다, 공용 검색 모달 하나로 통일하는 편이 장기적으로 더 일관되고 확장에 강하다고 정리했다. - 검색 입력과 실행 버튼은 세로로 같은 문법으로 쌓기보다, 입력은 입력끼리 실행은 액션으로 읽히게 한 줄 배치로 적당히 구분해주는 편이 운영 화면에서 덜 답답하다고 판단했다. diff --git a/docs/todo.md b/docs/todo.md index f300496..9d85627 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -1,6 +1,7 @@ # 할 일 및 이슈 ## 단기 확인 +- 아이템 관리 모달의 공용 게임 선택기에서는 이미 연결된 게임이 비활성화되므로, 실제 운영 데이터에서 중복 연결 방지와 `이미 추가됨` 표시가 기대대로 읽히는지 한 번 더 QA한다. - 공용 게임 선택 모달을 아이템 관리 모달에도 붙였으므로, 관리자 `게임 관리 / 전체 티어표 관리 / 아이템 관리` 세 흐름에서 같은 검색/선택 UX가 자연스럽게 느껴지는지 한 번 더 QA한다. - 관리자 `/admin/games?gameId=...` 직접 진입과 새로고침에서 선택 게임이 정상 복원되고 콘솔 오류가 없는지 한 번 더 QA한다. - 공용 `게임 선택` 검색 모달은 새로 붙였으므로, 게임 관리 선택/전체 티어표 관리 필터 양쪽에서 검색어 입력, 선택, 해제, 뒤로가기 흐름을 한 번 더 QA한다. diff --git a/docs/update.md b/docs/update.md index ea5ce08..8a65860 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,9 @@ # 업데이트 로그 +## 2026-04-02 v1.3.74 +- 아이템 관리 상세에서 템플릿 추가 대상 게임을 고를 때, 이미 해당 이미지가 연결된 게임은 공용 게임 선택 모달에서 `이미 추가됨`으로 표시하고 비활성화해 중복 추가 실수를 미리 막도록 정리함. +- 관리자 우측 사이드바 최하단에는 작은 카피라이트 문구를 추가해, 헤더에 관리 정보만 남기고 프로젝트 표기는 하단에서 조용히 보이도록 정리함. + ## 2026-04-02 v1.3.73 - 전체 티어표 관리 카드 썸네일은 `draggable="false"`로 바꿔, 미리보기 진입 시 브라우저 기본 이미지 드래그가 클릭을 방해하지 않도록 정리함. - 관리자 사이드바의 검색 입력과 검색 버튼은 한 줄로 묶어, 입력/선택/실행 버튼이 모두 같은 크기의 세로 스택처럼 보이던 답답함을 조금 줄이고 역할 구분을 더 분명하게 함. diff --git a/frontend/src/views/AdminView.vue b/frontend/src/views/AdminView.vue index dd5091a..9d0fa40 100644 --- a/frontend/src/views/AdminView.vue +++ b/frontend/src/views/AdminView.vue @@ -620,6 +620,7 @@ const imageDiagnosticsCards = computed(() => { const visibleLinkedGames = computed(() => (modalTargetCustomItem.value?.linkedGames || []).filter((game) => game?.id && game.id !== 'freeform') ) +const linkedCustomItemGameIds = computed(() => new Set(visibleLinkedGames.value.map((game) => game.id).filter(Boolean))) const imageStatsPeriodLabel = computed(() => (imageStatsMonth.value ? `${imageStatsMonth.value} 기준` : '전체 기간')) const imageStatsYearOptions = computed(() => { @@ -1309,6 +1310,7 @@ async function chooseGameFromPicker(gameId) { return } if (gamePickerMode.value === 'custom-item-target') { + if (linkedCustomItemGameIds.value.has(gameId)) return customItemModalTargetGameId.value = gameId closeGamePickerModal() return @@ -2035,12 +2037,21 @@ function userAvatarFallback(user) { v-for="game in filteredGamePickerGames" :key="game.id" class="adminGamePicker__item" - :class="{ 'adminGamePicker__item--active': gamePickerMode === 'tierlists-filter' ? adminTierListGameId === game.id : selectedGameId === game.id }" + :class="{ + 'adminGamePicker__item--active': gamePickerMode === 'tierlists-filter' + ? adminTierListGameId === game.id + : gamePickerMode === 'custom-item-target' + ? customItemModalTargetGameId === game.id + : selectedGameId === game.id, + 'adminGamePicker__item--disabled': gamePickerMode === 'custom-item-target' && linkedCustomItemGameIds.has(game.id), + }" type="button" + :disabled="gamePickerMode === 'custom-item-target' && linkedCustomItemGameIds.has(game.id)" @click="chooseGameFromPicker(game.id)" > {{ game.name }} + 이미 추가됨