Compare commits

..

2 Commits

Author SHA1 Message Date
Yu Sung
2e499483dd feat(agent): 소속 추가 다이얼로그 크리에이터 검색 디바운스 적용
- onSearchCreators에 300ms 디바운스 로직 추가
- assignDialog.searchDebounceTimer 상태 추가 및 다이얼로그/소멸 시 정리
- 최신 검색어와 응답 불일치 시 결과 반영 방지 가드
- 검색어 비었을 때 로딩/결과 초기화 처리 강화
2026-04-13 14:05:56 +09:00
Yu Sung
ceee1681c9 feat(agent): 에이전트-크리에이터 오류 메시지 표시 다이얼로그 추가 2026-04-13 13:59:26 +09:00

View File

@@ -289,6 +289,8 @@ export default {
searchQuery: '', searchQuery: '',
searchItems: [], searchItems: [],
searchLoading: false, searchLoading: false,
// 디바운스 타이머 보관
searchDebounceTimer: null,
}, },
unassignDialog: { unassignDialog: {
@@ -316,22 +318,50 @@ export default {
created() { created() {
this.fetchList(1) this.fetchList(1)
}, },
beforeDestroy() {
// 검색 디바운스 타이머 정리
if (this.assignDialog && this.assignDialog.searchDebounceTimer) {
clearTimeout(this.assignDialog.searchDebounceTimer)
this.assignDialog.searchDebounceTimer = null
}
},
methods: { methods: {
notifyError(message) {
// vuetify-dialog 플러그인을 통해 오류 노출
if (this.$dialog && this.$dialog.notify && this.$dialog.notify.error) {
this.$dialog.notify.error(message)
}
},
getErrorMessage(e) {
const fallback = '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.'
try {
if (!e) return fallback
// axios 형태의 에러 응답을 우선 사용
const msg = (e.response && e.response.data && (e.response.data.message || e.response.data.error || e.response.data.msg))
|| e.message
return msg || fallback
} catch (_) {
return fallback
}
},
async fetchList(page = this.page) { async fetchList(page = this.page) {
if (this.is_loading) return if (this.is_loading) return
this.is_loading = true this.is_loading = true
try { try {
this.page = page this.page = page
const res = await getAgentAssignedCreatorList(this.agentId, Math.max(1, this.page), this.page_size) const res = await getAgentAssignedCreatorList(this.agentId, Math.max(1, this.page), this.page_size)
// ApiResponse<GetAdminAgentAssignedCreatorResponse> if (res && res.status === 200 && res.data && res.data.success === true) {
let payload = res && res.data ? res.data : null const data = (res.data && res.data.data) || { totalCount: 0, items: [] }
if (payload && payload.data) payload = payload.data this.totalCount = data.totalCount || 0
const data = payload || { totalCount: 0, items: [] } this.items = Array.isArray(data.items) ? data.items : []
this.totalCount = data.totalCount || 0 } else {
this.items = Array.isArray(data.items) ? data.items : [] const msg = res && res.data && (res.data.message || res.data.error || res.data.msg)
this.notifyError(msg || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) { } catch (e) {
this.totalCount = 0 this.totalCount = 0
this.items = [] this.items = []
this.notifyError(this.getErrorMessage(e))
} finally { } finally {
this.is_loading = false this.is_loading = false
} }
@@ -345,41 +375,81 @@ export default {
}, },
closeAssignDialog() { closeAssignDialog() {
this.assignDialog.visible = false this.assignDialog.visible = false
// 다이얼로그 닫힐 때 디바운스 및 로딩 상태 초기화
if (this.assignDialog.searchDebounceTimer) {
clearTimeout(this.assignDialog.searchDebounceTimer)
this.assignDialog.searchDebounceTimer = null
}
this.assignDialog.searchLoading = false
}, },
async onSearchCreators(q) { onSearchCreators(q) {
const query = (q || '').trim() const query = (q || '').trim()
this.assignDialog.searchQuery = query this.assignDialog.searchQuery = query
// 기존 타이머가 있으면 취소
if (this.assignDialog.searchDebounceTimer) {
clearTimeout(this.assignDialog.searchDebounceTimer)
this.assignDialog.searchDebounceTimer = null
}
if (!query) { if (!query) {
// 검색어 없으면 결과/로딩 초기화
this.assignDialog.searchItems = [] this.assignDialog.searchItems = []
this.assignDialog.searchLoading = false
return return
} }
this.assignDialog.searchLoading = true
try { // 디바운스: 300ms 이후 최신 검색어로 요청
const res = await searchAdminAgentAssignableCreators(query) this.assignDialog.searchDebounceTimer = setTimeout(async () => {
let payload = res && res.data ? res.data : null // 요청 직전 로딩 시작
if (payload && payload.data) payload = payload.data this.assignDialog.searchLoading = true
const data = payload || { totalCount: 0, items: [] } const currentQuery = this.assignDialog.searchQuery
this.assignDialog.searchItems = Array.isArray(data.items) ? data.items : [] try {
} catch (e) { const res = await searchAdminAgentAssignableCreators(currentQuery)
this.assignDialog.searchItems = [] // 사용자가 그 사이에 검색어를 바꿨다면 이 응답은 무시
} finally { if (this.assignDialog.searchQuery !== currentQuery) return
this.assignDialog.searchLoading = false
} if (res && res.status === 200 && res.data && res.data.success === true) {
const data = (res.data && res.data.data) || { totalCount: 0, items: [] }
this.assignDialog.searchItems = Array.isArray(data.items) ? data.items : []
} else {
this.assignDialog.searchItems = []
const msg = res && res.data && (res.data.message || res.data.error || res.data.msg)
this.notifyError(msg || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
// 에러 시에도 최신 검색어와 일치할 때만 처리
if (this.assignDialog.searchQuery === currentQuery) {
this.assignDialog.searchItems = []
this.notifyError(this.getErrorMessage(e))
}
} finally {
// 최신 검색어와 일치할 때만 로딩 해제
if (this.assignDialog.searchQuery === currentQuery) {
this.assignDialog.searchLoading = false
}
}
}, 300)
}, },
async onAssign() { async onAssign() {
if (!this.canAssign) return if (!this.canAssign) return
this.assignDialog.loading = true this.assignDialog.loading = true
try { try {
const assignedAt = `${this.assignDialog.assignedDate}T00:00:00` const assignedAt = `${this.assignDialog.assignedDate}T00:00:00`
await assignAgentCreator({ const res = await assignAgentCreator({
agentId: Number(this.agentId), agentId: Number(this.agentId),
creatorId: Number(this.assignDialog.selectedCreatorId), creatorId: Number(this.assignDialog.selectedCreatorId),
assignedAt, assignedAt,
}) })
this.closeAssignDialog() if (res && res.status === 200 && res.data && res.data.success === true) {
this.fetchList(1) this.closeAssignDialog()
this.fetchList(1)
} else {
const msg = res && res.data && (res.data.message || res.data.error || res.data.msg)
this.notifyError(msg || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) { } catch (e) {
// noop: 에러 토스트 자리는 프로젝트 전역 플러그인 유무에 따라 추가 가능 this.notifyError(this.getErrorMessage(e))
} finally { } finally {
this.assignDialog.loading = false this.assignDialog.loading = false
} }
@@ -400,14 +470,19 @@ export default {
try { try {
const time = this.unassignDialog.time || '00:00' const time = this.unassignDialog.time || '00:00'
const unassignedAt = `${this.unassignDialog.date}T${time}:00` const unassignedAt = `${this.unassignDialog.date}T${time}:00`
await removeAgentCreator({ const res = await removeAgentCreator({
creatorId: Number(this.unassignDialog.target.creatorId), creatorId: Number(this.unassignDialog.target.creatorId),
unassignedAt, unassignedAt,
}) })
this.closeUnassignDialog() if (res && res.status === 200 && res.data && res.data.success === true) {
this.fetchList(this.page) this.closeUnassignDialog()
this.fetchList(this.page)
} else {
const msg = res && res.data && (res.data.message || res.data.error || res.data.msg)
this.notifyError(msg || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) { } catch (e) {
// noop this.notifyError(this.getErrorMessage(e))
} finally { } finally {
this.unassignDialog.loading = false this.unassignDialog.loading = false
} }