main
vue 133 lines 3.26 KB
Raw
1 <template>
2 <n-form label-placement="top">
3 <n-form-item label="Template">
4 <n-select
5 v-model:value="selectedTemplateId"
6 :options="templateOptions"
7 placeholder="Pick a template to apply"
8 :loading="loadingTemplates"
9 :render-label="renderOption"
10 to="body"
11 size="large"
12 filterable
13 />
14 </n-form-item>
15 <p class="text-xs">
16 Adds the template's tasks to this case. Existing tasks are preserved — you can layer multiple templates over
17 a single investigation.
18 </p>
19 <div class="mt-6 flex justify-end gap-2">
20 <n-button
21 type="primary"
22 :loading="applySubmitting"
23 :disabled="selectedTemplateId === null"
24 @click="submitApplyTemplate"
25 >
26 Apply
27 </n-button>
28 </div>
29 </n-form>
30 </template>
31
32 <script setup lang="ts">
33 import type { ApiError } from "@/types/common"
34 import type { CaseTemplate } from "@/types/incidentManagement/caseTemplates.d"
35 import { NButton, NForm, NFormItem, NSelect, useMessage } from "naive-ui"
36 import { computed, h, onBeforeMount, ref } from "vue"
37 import Api from "@/api"
38 import { getApiErrorMessage } from "@/utils"
39
40 const props = defineProps<{
41 caseId: number
42 customerCode?: string | null
43 }>()
44
45 const emit = defineEmits<{
46 (e: "success"): void
47 }>()
48
49 const message = useMessage()
50
51 const applySubmitting = ref(false)
52 const loadingTemplates = ref(false)
53 const availableTemplates = ref<CaseTemplate[]>([])
54 const selectedTemplateId = ref<number | null>(null)
55
56 const templateOptions = computed(() =>
57 availableTemplates.value.map(t => ({
58 label: t.name,
59 name: t.name,
60 customer_code: t.customer_code,
61 source: t.source,
62 is_default: t.is_default,
63 value: t.id
64 }))
65 )
66
67 function renderOption(option: {
68 label: string
69 value: number
70 name?: string | null
71 is_default?: boolean
72 customer_code?: string | null
73 source?: string | null
74 }) {
75 const title = `${option.name}${option.is_default ? " (default)" : ""}`
76 const description = [option.customer_code, option.source].filter(Boolean).join("")
77 return h("div", { class: "flex flex-col gap-0.5 leading-none py-2" }, [
78 h("span", title),
79 h("span", { class: "text-secondary text-xs" }, description)
80 ])
81 }
82
83 function openApplyTemplate() {
84 selectedTemplateId.value = null
85 loadingTemplates.value = true
86
87 Api.incidentManagement.caseTemplates
88 .listTemplates({
89 customerCode: props.customerCode ?? undefined,
90 includeGlobal: true
91 })
92 .then(res => {
93 if (res.data.success) {
94 availableTemplates.value = res.data.templates
95 } else {
96 message.warning(res.data.message)
97 }
98 })
99 .catch(err => {
100 message.error(getApiErrorMessage(err as ApiError) || "Failed to load templates")
101 })
102 .finally(() => {
103 loadingTemplates.value = false
104 })
105 }
106
107 async function submitApplyTemplate() {
108 if (selectedTemplateId.value === null) return
109
110 applySubmitting.value = true
111
112 try {
113 const res = await Api.incidentManagement.caseTemplates.applyTemplateToCase(
114 props.caseId,
115 selectedTemplateId.value
116 )
117 if (res.data.success) {
118 message.success(`Applied template — ${res.data.tasks_added} task(s) added`)
119 emit("success")
120 } else {
121 message.warning(res.data.message)
122 }
123 } catch (err) {
124 message.error(getApiErrorMessage(err as ApiError) || "Failed to apply template")
125 } finally {
126 applySubmitting.value = false
127 }
128 }
129
130 onBeforeMount(() => {
131 openApplyTemplate()
132 })
133 </script>