릴리스: v1.4.6 관리자 내부 명칭 정리 2차

This commit is contained in:
2026-04-02 18:43:39 +09:00
parent 1ed08d1e34
commit 20955e277c
10 changed files with 401 additions and 393 deletions

View File

@@ -15,13 +15,13 @@ export function useAdminCustomItems({
modalTargetCustomItem,
customItemModalDraftLabel,
customItemModalLabelSaving,
customItemModalTargetGameId,
games,
selectedGameId,
customItemModalTargetTemplateId,
templates,
selectedTemplateId,
refreshCustomItems,
loadGame,
loadTemplate,
setTab,
selectAdminGame,
selectAdminTemplate,
resetMessages,
success,
error,
@@ -59,7 +59,7 @@ export function useAdminCustomItems({
function openCustomItemModal(item) {
modalTargetCustomItem.value = item || null
customItemModalDraftLabel.value = item?.label || ''
customItemModalTargetGameId.value = ''
customItemModalTargetTemplateId.value = ''
customItemModalOpen.value = true
pushCustomItemModalHistoryState()
}
@@ -70,7 +70,7 @@ export function useAdminCustomItems({
modalTargetCustomItem.value = null
customItemModalDraftLabel.value = ''
customItemModalLabelSaving.value = false
customItemModalTargetGameId.value = ''
customItemModalTargetTemplateId.value = ''
if (fromPopState) {
customItemModalHistoryActive.value = false
@@ -97,12 +97,12 @@ export function useAdminCustomItems({
customItemDeleteModalOpen.value = false
}
function jumpToGameAdmin(gameId) {
if (!gameId) return
function jumpToTemplateAdmin(templateId) {
if (!templateId) return
closeCustomItemModal()
setTab('game-admin')
nextTick(() => {
selectAdminGame(gameId)
selectAdminTemplate(templateId)
})
}
@@ -160,18 +160,19 @@ export function useAdminCustomItems({
async function promoteCustomItem(item) {
resetMessages()
if (!customItemModalTargetGameId.value) {
if (!customItemModalTargetTemplateId.value) {
error.value = '추가할 템플릿을 먼저 선택해주세요.'
return
}
try {
item.isPromoting = true
await api.promoteAdminCustomItem(item.id, { gameId: customItemModalTargetGameId.value })
const targetGameName = games.value.find((game) => game.id === customItemModalTargetGameId.value)?.name || customItemModalTargetGameId.value
if (selectedGameId.value === customItemModalTargetGameId.value) await loadGame()
await api.promoteAdminCustomItem(item.id, { gameId: customItemModalTargetTemplateId.value })
const targetTemplateName =
templates.value.find((template) => template.id === customItemModalTargetTemplateId.value)?.name || customItemModalTargetTemplateId.value
if (selectedTemplateId.value === customItemModalTargetTemplateId.value) await loadTemplate()
closeCustomItemModal()
success.value = `"${item.label}" 이미지를 ${targetGameName} 템플릿으로 추가했어요.`
success.value = `"${item.label}" 이미지를 ${targetTemplateName} 템플릿으로 추가했어요.`
} catch (e) {
error.value = '선택한 이미지를 템플릿으로 추가하지 못했어요.'
} finally {
@@ -189,7 +190,7 @@ export function useAdminCustomItems({
closeCustomItemModal,
openCustomItemDeleteModal,
closeCustomItemDeleteModal,
jumpToGameAdmin,
jumpToTemplateAdmin,
removeCustomItem,
removeUnusedCustomItems,
saveCustomItemModalLabel,

View File

@@ -5,8 +5,8 @@ export function useAdminFeaturedGames({
api,
featuredListEl,
featuredSortable,
featuredGameIds,
games,
featuredTemplateIds,
templates,
resetMessages,
success,
error,
@@ -31,51 +31,51 @@ export function useAdminFeaturedGames({
chosenClass: 'chosen',
onEnd: (evt) => {
if (evt.oldIndex == null || evt.newIndex == null || evt.oldIndex === evt.newIndex) return
const nextIds = [...featuredGameIds.value]
const nextIds = [...featuredTemplateIds.value]
const [moved] = nextIds.splice(evt.oldIndex, 1)
nextIds.splice(evt.newIndex, 0, moved)
featuredGameIds.value = nextIds
featuredTemplateIds.value = nextIds
},
})
}
function addFeaturedGame(gameId) {
function addFeaturedTemplate(templateId) {
resetMessages()
if (!gameId || featuredGameIds.value.includes(gameId)) return
if (featuredGameIds.value.length >= 50) {
if (!templateId || featuredTemplateIds.value.includes(templateId)) return
if (featuredTemplateIds.value.length >= 50) {
error.value = '상단 고정 템플릿은 최대 50개까지만 설정할 수 있어요.'
return
}
featuredGameIds.value = [...featuredGameIds.value, gameId]
featuredTemplateIds.value = [...featuredTemplateIds.value, templateId]
syncFeaturedSortable()
}
function removeFeaturedGame(gameId) {
function removeFeaturedTemplate(templateId) {
resetMessages()
featuredGameIds.value = featuredGameIds.value.filter((id) => id !== gameId)
featuredTemplateIds.value = featuredTemplateIds.value.filter((id) => id !== templateId)
syncFeaturedSortable()
}
function moveFeaturedGame(gameId, direction) {
const currentIndex = featuredGameIds.value.indexOf(gameId)
function moveFeaturedTemplate(templateId, direction) {
const currentIndex = featuredTemplateIds.value.indexOf(templateId)
const nextIndex = currentIndex + direction
if (currentIndex < 0 || nextIndex < 0 || nextIndex >= featuredGameIds.value.length) return
const nextIds = [...featuredGameIds.value]
if (currentIndex < 0 || nextIndex < 0 || nextIndex >= featuredTemplateIds.value.length) return
const nextIds = [...featuredTemplateIds.value]
const [moved] = nextIds.splice(currentIndex, 1)
nextIds.splice(nextIndex, 0, moved)
featuredGameIds.value = nextIds
featuredTemplateIds.value = nextIds
syncFeaturedSortable()
}
async function saveFeaturedOrder() {
resetMessages()
try {
const data = await api.updateAdminGameDisplayOrder({ gameIds: featuredGameIds.value })
games.value = data.games || []
featuredGameIds.value = games.value
.filter((game) => game.displayRank != null)
const data = await api.updateAdminGameDisplayOrder({ gameIds: featuredTemplateIds.value })
templates.value = data.games || []
featuredTemplateIds.value = templates.value
.filter((template) => template.displayRank != null)
.sort((a, b) => a.displayRank - b.displayRank)
.map((game) => game.id)
.map((template) => template.id)
success.value = '홈 화면 템플릿 순서를 저장했어요.'
} catch (e) {
error.value = '템플릿 순서 저장에 실패했어요.'
@@ -85,9 +85,9 @@ export function useAdminFeaturedGames({
return {
destroyFeaturedSortable,
syncFeaturedSortable,
addFeaturedGame,
removeFeaturedGame,
moveFeaturedGame,
addFeaturedTemplate,
removeFeaturedTemplate,
moveFeaturedTemplate,
saveFeaturedOrder,
}
}

View File

@@ -4,8 +4,8 @@ import Sortable from 'sortablejs'
export function useAdminGameManager({
api,
toApiUrl,
selectedGameId,
selectedGame,
selectedTemplateId,
selectedTemplate,
uploadFiles,
uploadItemDrafts,
thumbFile,
@@ -18,15 +18,15 @@ export function useAdminGameManager({
activeTemplateRequest,
templateRequests,
customItemModalOpen,
customItemModalTargetGameId,
newGameId,
newGameName,
newGameIsPublic,
customItemModalTargetTemplateId,
newTemplateId,
newTemplateName,
newTemplateIsPublic,
clearPreviewUrl,
resetFileInput,
resetUploadState,
refreshGames,
closeGameCreateModal,
refreshTemplates,
closeTemplateCreateModal,
resetMessages,
success,
error,
@@ -59,7 +59,7 @@ export function useAdminGameManager({
async function syncGameItemSortable() {
await nextTick()
destroyGameItemSortable()
if (!gameItemListEl.value || !selectedGame.value?.items?.length) return
if (!gameItemListEl.value || !selectedTemplate.value?.items?.length) return
gameItemSortable.value = Sortable.create(gameItemListEl.value, {
animation: 160,
@@ -73,11 +73,11 @@ export function useAdminGameManager({
chosenClass: 'chosen',
onEnd: (evt) => {
if (evt.oldIndex == null || evt.newIndex == null || evt.oldIndex === evt.newIndex) return
const nextItems = [...(selectedGame.value?.items || [])]
const nextItems = [...(selectedTemplate.value?.items || [])]
const [moved] = nextItems.splice(evt.oldIndex, 1)
nextItems.splice(evt.newIndex, 0, moved)
selectedGame.value = {
...selectedGame.value,
selectedTemplate.value = {
...selectedTemplate.value,
items: nextItems,
}
},
@@ -87,7 +87,7 @@ export function useAdminGameManager({
function mergeRequestItemsIntoDrafts(request) {
const requestId = request?.id
if (!requestId) return
const existingGameSrcs = new Set((selectedGame.value?.items || []).map((item) => normalizeDraftSrc(item?.src)).filter(Boolean))
const existingTemplateSrcs = new Set((selectedTemplate.value?.items || []).map((item) => normalizeDraftSrc(item?.src)).filter(Boolean))
const existingKeys = new Set(uploadItemDrafts.value.map((draft) => `${draft.kind}:${draft.requestId || ''}:${draft.itemId || draft.file?.name || ''}`))
const nextRequestDrafts = (request.items || [])
.filter((item) => item?.src)
@@ -100,7 +100,7 @@ export function useAdminGameManager({
sourceName: requestItemFilename(item),
src: item.src,
}))
.filter((draft) => !existingGameSrcs.has(normalizeDraftSrc(draft.src)))
.filter((draft) => !existingTemplateSrcs.has(normalizeDraftSrc(draft.src)))
.filter((draft) => !existingKeys.has(`${draft.kind}:${draft.requestId}:${draft.itemId}`))
if (nextRequestDrafts.length) {
@@ -117,13 +117,13 @@ export function useAdminGameManager({
uploadFiles.value = uploadItemDrafts.value.filter((draft) => draft.kind === 'file').map((draft) => draft.file).filter(Boolean)
}
async function loadGame(options = {}) {
async function loadTemplate(options = {}) {
const preserveUploadState = !!options.preserveUploadState
resetMessages()
if (!preserveUploadState) resetUploadState()
if (!selectedGameId.value) {
selectedGame.value = null
if (!selectedTemplateId.value) {
selectedTemplate.value = null
savedGameItemOrderIds.value = []
destroyGameItemSortable()
return
@@ -131,8 +131,8 @@ export function useAdminGameManager({
try {
isGameLoading.value = true
const data = await api.getGame(selectedGameId.value)
selectedGame.value = {
const data = await api.getGame(selectedTemplateId.value)
selectedTemplate.value = {
...data,
items: (data.items || []).map((item) => ({
...item,
@@ -142,16 +142,16 @@ export function useAdminGameManager({
savedGameItemOrderIds.value = (data.items || []).map((item) => item.id)
await syncGameItemSortable()
} catch (e) {
selectedGame.value = null
selectedTemplate.value = null
error.value = '템플릿 정보를 불러오지 못했어요.'
} finally {
isGameLoading.value = false
}
}
async function createGame(options = {}) {
const nextGameId = typeof options.gameId === 'string' ? options.gameId.trim() : newGameId.value.trim()
const nextGameName = typeof options.gameName === 'string' ? options.gameName.trim() : newGameName.value.trim()
async function createTemplate(options = {}) {
const nextGameId = typeof options.gameId === 'string' ? options.gameId.trim() : newTemplateId.value.trim()
const nextGameName = typeof options.gameName === 'string' ? options.gameName.trim() : newTemplateName.value.trim()
const preserveUploadState = !!options.preserveUploadState
resetMessages()
try {
@@ -162,7 +162,7 @@ export function useAdminGameManager({
body: JSON.stringify({
id: nextGameId,
name: nextGameName,
isPublic: !!newGameIsPublic.value,
isPublic: !!newTemplateIsPublic.value,
thumbnailSrc: activeTemplateRequest.value?.type === 'create' ? (activeTemplateRequest.value?.thumbnailSrc || '') : '',
}),
})
@@ -187,11 +187,11 @@ export function useAdminGameManager({
})
}
}
await refreshGames()
selectedGameId.value = data.game.id
if (customItemModalOpen.value) customItemModalTargetGameId.value = data.game.id
closeGameCreateModal()
await loadGame({ preserveUploadState })
await refreshTemplates()
selectedTemplateId.value = data.game.id
if (customItemModalOpen.value) customItemModalTargetTemplateId.value = data.game.id
closeTemplateCreateModal()
await loadTemplate({ preserveUploadState })
if (!preserveUploadState && activeTemplateRequest.value?.id) {
const sourceRequest = templateRequests.value.find((entry) => entry.id === activeTemplateRequest.value.id) || activeTemplateRequest.value
mergeRequestItemsIntoDrafts(sourceRequest)
@@ -254,21 +254,21 @@ export function useAdminGameManager({
}
try {
if (!selectedGameId.value && activeTemplateRequest.value?.type === 'create' && !activeTemplateRequest.value?.targetGameId) {
if (!selectedTemplateId.value && activeTemplateRequest.value?.type === 'create' && !activeTemplateRequest.value?.targetGameId) {
const draftGameId = (activeTemplateRequest.value?.draftGameId || '').trim()
const draftGameName = (activeTemplateRequest.value?.draftGameName || '').trim()
if (!draftGameId || !draftGameName) {
error.value = '먼저 신규 템플릿의 이름과 템플릿 ID를 저장해주세요.'
return
}
await createGame({
await createTemplate({
gameId: draftGameId,
gameName: draftGameName,
preserveUploadState: true,
})
}
if (!selectedGameId.value) {
if (!selectedTemplateId.value) {
error.value = '템플릿을 먼저 선택해주세요.'
return
}
@@ -283,7 +283,7 @@ export function useAdminGameManager({
fd.append('images', entry.file)
fd.append('labels', entry.label.trim())
})
const res = await fetch(toApiUrl(`/api/admin/games/${encodeURIComponent(selectedGameId.value)}/images`), {
const res = await fetch(toApiUrl(`/api/admin/games/${encodeURIComponent(selectedTemplateId.value)}/images`), {
method: 'POST',
credentials: 'include',
body: fd,
@@ -297,7 +297,7 @@ export function useAdminGameManager({
for (const requestId of requestIds) {
const draftsForRequest = requestDrafts.filter((entry) => entry.requestId === requestId)
const result = await api.promoteAdminTemplateRequestItems(requestId, {
gameId: selectedGameId.value,
gameId: selectedTemplateId.value,
itemIds: draftsForRequest.map((entry) => entry.itemId).filter(Boolean),
itemSrcs: draftsForRequest.map((entry) => entry.src).filter(Boolean),
itemLabels: draftsForRequest.reduce((acc, entry) => {
@@ -310,7 +310,7 @@ export function useAdminGameManager({
}
resetUploadState()
await loadGame()
await loadTemplate()
success.value = `템플릿 기본 아이템 ${uploadCount}개 추가를 완료했어요.`
} catch (e) {
const apiError = e?.data?.error || ''
@@ -331,16 +331,16 @@ export function useAdminGameManager({
}
}
async function saveGameItemOrder() {
async function saveTemplateItemOrder() {
resetMessages()
if (!selectedGameId.value || !selectedGame.value?.items?.length) return
if (!selectedTemplateId.value || !selectedTemplate.value?.items?.length) return
try {
const data = await api.updateAdminGameItemDisplayOrder(selectedGameId.value, {
itemIds: selectedGame.value.items.map((item) => item.id),
const data = await api.updateAdminGameItemDisplayOrder(selectedTemplateId.value, {
itemIds: selectedTemplate.value.items.map((item) => item.id),
})
selectedGame.value = {
...selectedGame.value,
selectedTemplate.value = {
...selectedTemplate.value,
items: (data.items || []).map((item) => ({
...item,
draftLabel: item.label,
@@ -360,13 +360,13 @@ export function useAdminGameManager({
syncGameItemSortable,
mergeRequestItemsIntoDrafts,
removeUploadDraft,
loadGame,
createGame,
loadTemplate,
createTemplate,
handleItemFiles,
onFile,
openItemFilePicker,
clearItemFiles,
uploadItem,
saveGameItemOrder,
saveTemplateItemOrder,
}
}

View File

@@ -3,10 +3,10 @@ export function useAdminTemplateRequests({
activeTemplateRequest,
refreshTemplateRequests,
setTab,
openGameCreateModal,
newGameId,
newGameName,
selectAdminGame,
openTemplateCreateModal,
newTemplateId,
newTemplateName,
selectAdminTemplate,
mergeRequestItemsIntoDrafts,
resetMessages,
success,
@@ -65,16 +65,16 @@ export function useAdminTemplateRequests({
if (request.type === 'create') {
const linkedGameId = syncedRequest.targetGameId || ''
if (linkedGameId) {
await selectAdminGame(linkedGameId)
await selectAdminTemplate(linkedGameId)
} else {
openGameCreateModal()
newGameId.value = (syncedRequest.draftGameId || '').trim()
newGameName.value = (syncedRequest.draftGameName || '').trim()
openTemplateCreateModal()
newTemplateId.value = (syncedRequest.draftGameId || '').trim()
newTemplateName.value = (syncedRequest.draftGameName || '').trim()
}
mergeRequestItemsIntoDrafts(syncedRequest)
} else {
const nextGameId = syncedRequest.targetGameId || syncedRequest.sourceGameId || ''
if (nextGameId) await selectAdminGame(nextGameId)
if (nextGameId) await selectAdminTemplate(nextGameId)
mergeRequestItemsIntoDrafts(syncedRequest)
}
success.value = '요청 아이템을 템플릿 관리 화면으로 가져왔어요. 필요한 항목만 골라서 추가해 주세요.'