341 lines
12 KiB
JavaScript
341 lines
12 KiB
JavaScript
export function useAdminCustomItems({
|
|
api,
|
|
toast,
|
|
customItems,
|
|
customItemPage,
|
|
customItemLimit,
|
|
customItemPageCount,
|
|
customItemQuery,
|
|
customItemFilter,
|
|
customItemModalOpen,
|
|
customItemDeleteModalOpen,
|
|
customItemModalHistoryActive,
|
|
modalTargetCustomItem,
|
|
customItemModalDraftLabel,
|
|
customItemModalDraftTags,
|
|
customItemModalLabelSaving,
|
|
customItemModalTargetTemplateId,
|
|
customItemReplacementQuery,
|
|
customItemReplacementItems,
|
|
customItemReplacementLoading,
|
|
customItemReplacementTargetId,
|
|
customItemReplacementBusy,
|
|
templates,
|
|
selectedTemplateId,
|
|
refreshCustomItems,
|
|
loadTemplate,
|
|
resetMessages,
|
|
success,
|
|
error,
|
|
}) {
|
|
function submitCustomItemSearch() {
|
|
customItemPage.value = 1
|
|
refreshCustomItems()
|
|
}
|
|
|
|
function changeCustomItemFilter(filter) {
|
|
customItemFilter.value = filter
|
|
customItemPage.value = 1
|
|
refreshCustomItems()
|
|
}
|
|
|
|
function changeCustomItemLimit(limit) {
|
|
customItemLimit.value = limit
|
|
customItemPage.value = 1
|
|
refreshCustomItems()
|
|
}
|
|
|
|
function moveCustomItemPage(direction) {
|
|
const nextPage = customItemPage.value + direction
|
|
if (nextPage < 1 || nextPage > customItemPageCount.value) return
|
|
customItemPage.value = nextPage
|
|
refreshCustomItems()
|
|
}
|
|
|
|
function pushCustomItemModalHistoryState() {
|
|
if (typeof window === 'undefined') return
|
|
window.history.pushState({ ...(window.history.state || {}), adminCustomItemModal: true }, '', window.location.href)
|
|
customItemModalHistoryActive.value = true
|
|
}
|
|
|
|
async function refreshReplacementCandidates() {
|
|
const currentItemId = modalTargetCustomItem.value?.id || ''
|
|
if (!currentItemId) {
|
|
customItemReplacementItems.value = []
|
|
return
|
|
}
|
|
|
|
try {
|
|
customItemReplacementLoading.value = true
|
|
const data = await api.listAdminCustomItems({
|
|
q: customItemReplacementQuery.value,
|
|
page: 1,
|
|
limit: 50,
|
|
filter: 'all',
|
|
collapseShared: true,
|
|
})
|
|
customItemReplacementItems.value = (data.items || []).filter((item) => item?.id && item.id !== currentItemId)
|
|
} catch (e) {
|
|
error.value = '대체할 이미지 목록을 불러오지 못했어요.'
|
|
customItemReplacementItems.value = []
|
|
} finally {
|
|
customItemReplacementLoading.value = false
|
|
}
|
|
}
|
|
|
|
function openCustomItemModal(item) {
|
|
modalTargetCustomItem.value = item || null
|
|
customItemModalDraftLabel.value = item?.label || ''
|
|
customItemModalDraftTags.value = Array.isArray(item?.tags) ? [...item.tags] : []
|
|
customItemModalTargetTemplateId.value = ''
|
|
customItemReplacementQuery.value = ''
|
|
customItemReplacementItems.value = []
|
|
customItemReplacementTargetId.value = ''
|
|
customItemReplacementBusy.value = false
|
|
customItemModalOpen.value = true
|
|
pushCustomItemModalHistoryState()
|
|
}
|
|
|
|
function closeCustomItemModal({ fromPopState = false } = {}) {
|
|
customItemModalOpen.value = false
|
|
customItemDeleteModalOpen.value = false
|
|
modalTargetCustomItem.value = null
|
|
customItemModalDraftLabel.value = ''
|
|
customItemModalDraftTags.value = []
|
|
customItemModalLabelSaving.value = false
|
|
customItemModalTargetTemplateId.value = ''
|
|
customItemReplacementQuery.value = ''
|
|
customItemReplacementItems.value = []
|
|
customItemReplacementTargetId.value = ''
|
|
customItemReplacementLoading.value = false
|
|
customItemReplacementBusy.value = false
|
|
|
|
if (fromPopState) {
|
|
customItemModalHistoryActive.value = false
|
|
return
|
|
}
|
|
|
|
if (customItemModalHistoryActive.value && typeof window !== 'undefined') {
|
|
customItemModalHistoryActive.value = false
|
|
window.history.back()
|
|
}
|
|
}
|
|
|
|
function openCustomItemDeleteModal(item) {
|
|
if (!item) return
|
|
if (item.sourceType === 'user' && !item.replacedAt && (item.usageCount > 0 || item.linkedTemplates.length > 0)) {
|
|
error.value = '사용 중이거나 템플릿에 연결된 사용자 업로드 이미지는 먼저 참조를 정리해야 삭제할 수 있어요.'
|
|
return
|
|
}
|
|
modalTargetCustomItem.value = item
|
|
customItemDeleteModalOpen.value = true
|
|
}
|
|
|
|
function closeCustomItemDeleteModal() {
|
|
customItemDeleteModalOpen.value = false
|
|
}
|
|
|
|
async function removeCustomItem(item = modalTargetCustomItem.value) {
|
|
resetMessages()
|
|
if (!item) return
|
|
if (item.sourceType === 'user' && !item.replacedAt && (item.usageCount > 0 || item.linkedTemplates.length > 0)) {
|
|
error.value = '사용 중이거나 템플릿에 연결된 사용자 업로드 이미지는 먼저 참조를 정리해야 삭제할 수 있어요.'
|
|
return
|
|
}
|
|
|
|
try {
|
|
await api.deleteAdminCustomItem(item.id)
|
|
closeCustomItemDeleteModal()
|
|
closeCustomItemModal()
|
|
await refreshCustomItems()
|
|
success.value =
|
|
item.sourceType === 'template'
|
|
? '선택한 템플릿 아이템을 제거했어요.'
|
|
: item.sourceType === 'asset'
|
|
? '선택한 이미지 자산을 삭제했어요.'
|
|
: '사용자 업로드 이미지를 삭제했어요.'
|
|
} catch (e) {
|
|
error.value =
|
|
item.sourceType === 'template'
|
|
? '템플릿 아이템 제거에 실패했어요.'
|
|
: item.sourceType === 'asset'
|
|
? '이미지 자산 삭제에 실패했어요.'
|
|
: '사용자 업로드 이미지 삭제에 실패했어요.'
|
|
}
|
|
}
|
|
|
|
async function removeUnusedCustomItems() {
|
|
resetMessages()
|
|
const ok = window.confirm('현재 검색 조건에 맞는 미사용 이미지를 모두 삭제할까요?')
|
|
if (!ok) return
|
|
|
|
try {
|
|
const data = await api.deleteAdminUnusedCustomItems({ q: customItemQuery.value })
|
|
await refreshCustomItems()
|
|
success.value = `${data.deletedCount || 0}개의 미사용 이미지를 삭제했어요.`
|
|
} catch (e) {
|
|
error.value = '미사용 이미지 일괄 삭제에 실패했어요.'
|
|
}
|
|
}
|
|
|
|
function showUnusedCustomItems() {
|
|
if (customItemFilter.value === 'unused') return
|
|
resetMessages()
|
|
customItemFilter.value = 'unused'
|
|
customItemPage.value = 1
|
|
refreshCustomItems()
|
|
}
|
|
|
|
async function saveCustomItemModalLabel() {
|
|
const item = modalTargetCustomItem.value
|
|
const nextLabel = customItemModalDraftLabel.value.trim().slice(0, 60)
|
|
const nextTags = Array.from(
|
|
new Set(
|
|
(Array.isArray(customItemModalDraftTags.value) ? customItemModalDraftTags.value : [])
|
|
.map((tag) => tag.trim().replace(/^#/, '').slice(0, 40))
|
|
.filter(Boolean)
|
|
)
|
|
).slice(0, 30)
|
|
if (
|
|
!item ||
|
|
!nextLabel ||
|
|
(nextLabel === item.label && JSON.stringify(nextTags) === JSON.stringify(Array.isArray(item.tags) ? item.tags : [])) ||
|
|
customItemModalLabelSaving.value
|
|
) {
|
|
return
|
|
}
|
|
|
|
try {
|
|
customItemModalLabelSaving.value = true
|
|
const data = await api.updateAdminCustomItemLabel(item.id, { label: nextLabel, tags: nextTags, sourceType: item.sourceType })
|
|
item.label = data.item?.label || nextLabel
|
|
item.tags = Array.isArray(data.item?.tags) ? data.item.tags : nextTags
|
|
customItemModalDraftLabel.value = item.label
|
|
customItemModalDraftTags.value = [...item.tags]
|
|
customItems.value = customItems.value.map((entry) => (entry.id === item.id ? { ...entry, label: item.label, tags: item.tags } : entry))
|
|
toast.success('아이템 메타를 변경했어요.')
|
|
} catch (e) {
|
|
error.value = '아이템 메타 변경에 실패했어요.'
|
|
} finally {
|
|
customItemModalLabelSaving.value = false
|
|
}
|
|
}
|
|
|
|
async function promoteCustomItem(item) {
|
|
resetMessages()
|
|
if (!customItemModalTargetTemplateId.value) {
|
|
error.value = '추가할 템플릿을 먼저 선택해주세요.'
|
|
return
|
|
}
|
|
|
|
try {
|
|
item.isPromoting = true
|
|
await api.promoteAdminTemplateItem(item.id, { topicId: 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}" 이미지를 ${targetTemplateName} 템플릿으로 추가했어요.`
|
|
} catch (e) {
|
|
error.value = '선택한 이미지를 템플릿으로 추가하지 못했어요.'
|
|
} finally {
|
|
item.isPromoting = false
|
|
}
|
|
}
|
|
|
|
async function unlinkCustomItemTemplate(item = modalTargetCustomItem.value, template) {
|
|
resetMessages()
|
|
if (!item?.id || !template?.id) {
|
|
error.value = '제외할 템플릿 정보를 찾지 못했어요.'
|
|
return
|
|
}
|
|
|
|
const ok = window.confirm(`"${template.name}" 템플릿에서 이 이미지를 제외할까요?`)
|
|
if (!ok) return
|
|
|
|
try {
|
|
await api.unlinkAdminCustomItemTemplate(item.id, { topicId: template.id })
|
|
if (selectedTemplateId.value === template.id) await loadTemplate()
|
|
await refreshCustomItems()
|
|
modalTargetCustomItem.value = {
|
|
...item,
|
|
linkedTemplates: (item.linkedTemplates || []).filter((entry) => entry.id !== template.id),
|
|
}
|
|
success.value = `"${template.name}" 템플릿에서 이미지를 제외했어요.`
|
|
} catch (e) {
|
|
error.value = '템플릿 연결 해제에 실패했어요.'
|
|
}
|
|
}
|
|
|
|
async function replaceCustomItem(item = modalTargetCustomItem.value) {
|
|
resetMessages()
|
|
const targetItem = customItemReplacementItems.value.find((entry) => entry.id === customItemReplacementTargetId.value)
|
|
if (!item?.id) {
|
|
error.value = '대체할 원본 아이템을 찾지 못했어요.'
|
|
return
|
|
}
|
|
if (!targetItem?.id) {
|
|
error.value = '대체할 대상 이미지를 먼저 선택해주세요.'
|
|
return
|
|
}
|
|
|
|
try {
|
|
customItemReplacementBusy.value = true
|
|
const data = await api.replaceAdminCustomItem(item.id, {
|
|
targetItemId: targetItem.id,
|
|
targetSourceType: targetItem.sourceType || 'user',
|
|
})
|
|
if (selectedTemplateId.value) await loadTemplate()
|
|
await refreshCustomItems()
|
|
closeCustomItemModal()
|
|
success.value = `"${item.label}" 이미지를 "${data.targetItem?.label || targetItem.label}" 기준으로 대체했어요.`
|
|
} catch (e) {
|
|
error.value = e?.status === 409 ? '같은 이미지/이름으로는 대체할 수 없어요.' : '이미지 대체에 실패했어요.'
|
|
} finally {
|
|
customItemReplacementBusy.value = false
|
|
}
|
|
}
|
|
|
|
async function restoreCustomItem(item = modalTargetCustomItem.value) {
|
|
resetMessages()
|
|
if (!item?.id || !item.replacedAt) {
|
|
error.value = '복구할 대체 이력이 없어요.'
|
|
return
|
|
}
|
|
|
|
try {
|
|
customItemReplacementBusy.value = true
|
|
await api.restoreAdminCustomItem(item.id)
|
|
if (selectedTemplateId.value) await loadTemplate()
|
|
await refreshCustomItems()
|
|
closeCustomItemModal()
|
|
success.value = `"${item.label}" 아이템을 원래 이미지로 복구했어요.`
|
|
} catch (e) {
|
|
error.value = '원래 이미지로 복구하지 못했어요.'
|
|
} finally {
|
|
customItemReplacementBusy.value = false
|
|
}
|
|
}
|
|
|
|
return {
|
|
submitCustomItemSearch,
|
|
changeCustomItemFilter,
|
|
changeCustomItemLimit,
|
|
moveCustomItemPage,
|
|
pushCustomItemModalHistoryState,
|
|
openCustomItemModal,
|
|
closeCustomItemModal,
|
|
openCustomItemDeleteModal,
|
|
closeCustomItemDeleteModal,
|
|
removeCustomItem,
|
|
removeUnusedCustomItems,
|
|
showUnusedCustomItems,
|
|
saveCustomItemModalLabel,
|
|
promoteCustomItem,
|
|
unlinkCustomItemTemplate,
|
|
refreshReplacementCandidates,
|
|
replaceCustomItem,
|
|
restoreCustomItem,
|
|
}
|
|
}
|