feat: alert/confirm을 공통 모달 다이얼로그로 통합
모든 window.alert/confirm 을 업무 삭제 모달과 동일한 구조/디자인의 공통 모달로 대체하고, 한 곳에서 관리하도록 다이얼로그 시스템을 도입한다. - dialog.store: 알림/확인 큐 + Promise 기반 상태 관리 - useDialog(): alert/confirm 컴포저블 API - AppDialog: App 루트 1회 마운트, 전역 .modal-*/.mbtn 클래스로 렌더 (variant: info/success/warning/danger → 아이콘·버튼 색) - relay.css: modal-ico.info/.warning, mbtn.primary 변형 추가 - 교체: API 에러 알림(useApi), 검증/성공 알림(Signup/ProjectCreate/ ProjectSettings/TaskCreate), 삭제·제거 확인(ProjectSettings/ ProjectMembers/AgentChatPanel) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
import { nextTick, ref, watch } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useAiStore } from '@/stores/ai.store'
|
||||
import { useDialog } from '@/composables/useDialog'
|
||||
import { relativeTimeKo } from '@/shared/utils/format'
|
||||
import { renderMarkdown } from '@/shared/utils/markdown'
|
||||
|
||||
@@ -18,6 +19,7 @@ interface Emits {
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const aiStore = useAiStore()
|
||||
const dialog = useDialog()
|
||||
const { conversations, listLoading, messages, loading, error } =
|
||||
storeToRefs(aiStore)
|
||||
|
||||
@@ -66,7 +68,11 @@ async function openConv(id: string) {
|
||||
}
|
||||
|
||||
async function removeConv(id: string) {
|
||||
if (!window.confirm('이 대화를 삭제할까요?')) return
|
||||
const ok = await dialog.confirm('이 대화를 삭제할까요?', {
|
||||
variant: 'danger',
|
||||
confirmText: '삭제',
|
||||
})
|
||||
if (!ok) return
|
||||
await aiStore.removeConversation(id)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
<script setup lang="ts">
|
||||
// 전역 공통 다이얼로그 — App 루트에 1회 마운트. dialog.store 상태를 받아
|
||||
// 업무 삭제 모달과 동일한 구조/디자인(.modal-* / .mbtn)으로 렌더한다.
|
||||
import { computed } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useDialogStore } from '@/stores/dialog.store'
|
||||
|
||||
const store = useDialogStore()
|
||||
const { current } = storeToRefs(store)
|
||||
|
||||
// variant → 아이콘 원형 색 클래스(danger 는 기본 빨강)
|
||||
const iconClass = computed(() => {
|
||||
switch (current.value?.variant) {
|
||||
case 'success':
|
||||
return 'approve'
|
||||
case 'warning':
|
||||
return 'warning'
|
||||
case 'info':
|
||||
return 'info'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
})
|
||||
|
||||
// 확인 버튼 색 — 알림(OK)은 항상 중립 강조(primary), 확인은 variant 따름
|
||||
const confirmClass = computed(() => {
|
||||
if (current.value?.mode === 'alert') return 'primary'
|
||||
switch (current.value?.variant) {
|
||||
case 'success':
|
||||
return 'approve'
|
||||
case 'danger':
|
||||
return 'danger'
|
||||
default:
|
||||
return 'primary'
|
||||
}
|
||||
})
|
||||
|
||||
function onConfirm() {
|
||||
store.settle(true)
|
||||
}
|
||||
function onCancel() {
|
||||
store.settle(false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div
|
||||
class="modal-backdrop"
|
||||
:class="{ open: !!current }"
|
||||
@click.self="onCancel"
|
||||
>
|
||||
<div
|
||||
v-if="current"
|
||||
class="modal"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
>
|
||||
<div class="modal-body">
|
||||
<div
|
||||
class="modal-ico"
|
||||
:class="iconClass"
|
||||
>
|
||||
<svg
|
||||
v-if="current.variant === 'success'"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
><path d="M20 6 9 17l-5-5" /></svg>
|
||||
<svg
|
||||
v-else-if="current.variant === 'info'"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
><circle
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
/><path d="M12 16v-4M12 8h.01" /></svg>
|
||||
<svg
|
||||
v-else
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
><path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z" /><path d="M12 9v4M12 17h.01" /></svg>
|
||||
</div>
|
||||
<h2 class="modal-title">
|
||||
{{ current.title }}
|
||||
</h2>
|
||||
<p
|
||||
v-if="current.desc"
|
||||
class="modal-desc"
|
||||
>
|
||||
{{ current.desc }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-foot">
|
||||
<button
|
||||
v-if="current.mode === 'confirm'"
|
||||
class="mbtn"
|
||||
type="button"
|
||||
@click="onCancel"
|
||||
>
|
||||
{{ current.cancelText }}
|
||||
</button>
|
||||
<button
|
||||
class="mbtn"
|
||||
:class="confirmClass"
|
||||
type="button"
|
||||
@click="onConfirm"
|
||||
>
|
||||
{{ current.confirmText }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
Reference in New Issue
Block a user