| 1 | <template> |
| 2 | <n-spin :show="loading" class="flex min-h-48 grow flex-col" content-class="flex grow flex-col gap-4"> |
| 3 | <div> |
| 4 | <n-collapse-transition :show="!showUploadForm"> |
| 5 | <div class="flex flex-wrap items-center gap-3"> |
| 6 | <n-button v-if="templateNameList.length" type="primary" @click="openUploadForm()"> |
| 7 | <template #icon> |
| 8 | <Icon :name="UploadIcon" /> |
| 9 | </template> |
| 10 | Upload Report Template file |
| 11 | </n-button> |
| 12 | |
| 13 | <n-button |
| 14 | v-if="!isDefaultTemplatePresent && templateNameList.length" |
| 15 | :loading="checkingDefaultTemplate" |
| 16 | @click="uploadDefaultTemplate()" |
| 17 | > |
| 18 | <template #icon> |
| 19 | <Icon :name="DefaultTemplateIcon" /> |
| 20 | </template> |
| 21 | Load the SOCFortress Template |
| 22 | </n-button> |
| 23 | </div> |
| 24 | </n-collapse-transition> |
| 25 | |
| 26 | <n-collapse-transition :show="showUploadForm"> |
| 27 | <div class="flex flex-col gap-2"> |
| 28 | <n-upload |
| 29 | v-model:file-list="fileList" |
| 30 | :max="1" |
| 31 | :disabled="uploading" |
| 32 | accept="application/vnd.openxmlformats-officedocument.wordprocessingml.document, .docx, .DOCX, text/html, .html, .HTML" |
| 33 | > |
| 34 | <n-upload-dragger> |
| 35 | <div> |
| 36 | <Icon :name="UploadIcon" :size="28" :depth="3" /> |
| 37 | </div> |
| 38 | <div class="font-semibold">Click or drag a file to this area to upload</div> |
| 39 | <p class="mt-2"> |
| 40 | Only |
| 41 | <strong>.docx</strong> |
| 42 | and |
| 43 | <strong>.html</strong> |
| 44 | files are accepted |
| 45 | </p> |
| 46 | </n-upload-dragger> |
| 47 | </n-upload> |
| 48 | |
| 49 | <div class="flex items-center justify-end gap-3"> |
| 50 | <n-button quaternary :disabled="uploading" @click="closeUploadForm()">Close</n-button> |
| 51 | |
| 52 | <n-button |
| 53 | :disabled="!isValid" |
| 54 | :loading="uploading" |
| 55 | type="primary" |
| 56 | @click="uploadCustomTemplate()" |
| 57 | > |
| 58 | <template #icon> |
| 59 | <Icon :name="UploadIcon" /> |
| 60 | </template> |
| 61 | |
| 62 | Upload |
| 63 | </n-button> |
| 64 | </div> |
| 65 | </div> |
| 66 | </n-collapse-transition> |
| 67 | </div> |
| 68 | |
| 69 | <div class="flex flex-col gap-2"> |
| 70 | <template v-if="templateNameList.length"> |
| 71 | <n-card |
| 72 | v-for="templateName of templateNameList" |
| 73 | :key="templateName" |
| 74 | size="small" |
| 75 | class="overflow-hidden" |
| 76 | embedded |
| 77 | content-class="flex flex-wrap items-center justify-between gap-4" |
| 78 | > |
| 79 | <div class="flex gap-3"> |
| 80 | <n-tooltip to="body"> |
| 81 | <template #trigger> |
| 82 | <n-button |
| 83 | size="tiny" |
| 84 | type="primary" |
| 85 | text |
| 86 | :loading="downloading === templateName" |
| 87 | @click="downloadTemplate(templateName)" |
| 88 | > |
| 89 | <template #icon> |
| 90 | <Icon :name="DownloadIcon" /> |
| 91 | </template> |
| 92 | </n-button> |
| 93 | </template> |
| 94 | Download |
| 95 | </n-tooltip> |
| 96 | <span> |
| 97 | {{ templateName }} |
| 98 | </span> |
| 99 | </div> |
| 100 | |
| 101 | <n-popconfirm to="body" @positive-click="deleteTemplate(templateName)"> |
| 102 | <template #trigger> |
| 103 | <n-button quaternary size="tiny">Delete</n-button> |
| 104 | </template> |
| 105 | Are you sure you want to delete this template? |
| 106 | </n-popconfirm> |
| 107 | </n-card> |
| 108 | </template> |
| 109 | <template v-else> |
| 110 | <n-collapse-transition :show="!showUploadForm"> |
| 111 | <n-empty v-if="!loading" class="min-h-48"> |
| 112 | <div class="flex flex-col items-center gap-4"> |
| 113 | <p>No templates found</p> |
| 114 | <div class="flex flex-wrap items-center gap-3"> |
| 115 | <n-button type="primary" :loading="uploading" @click="openUploadForm()"> |
| 116 | <template #icon> |
| 117 | <Icon :name="UploadIcon" /> |
| 118 | </template> |
| 119 | Upload a Report Template file |
| 120 | </n-button> |
| 121 | <n-button :loading="uploading" @click="uploadDefaultTemplate()"> |
| 122 | <template #icon> |
| 123 | <Icon :name="DefaultTemplateIcon" /> |
| 124 | </template> |
| 125 | Load the SOCFortress Template |
| 126 | </n-button> |
| 127 | </div> |
| 128 | </div> |
| 129 | </n-empty> |
| 130 | </n-collapse-transition> |
| 131 | </template> |
| 132 | </div> |
| 133 | </n-spin> |
| 134 | </template> |
| 135 | |
| 136 | <script setup lang="ts"> |
| 137 | import type { UploadFileInfo } from "naive-ui" |
| 138 | import saveAs from "file-saver" |
| 139 | import { |
| 140 | NButton, |
| 141 | NCard, |
| 142 | NCollapseTransition, |
| 143 | NEmpty, |
| 144 | NPopconfirm, |
| 145 | NSpin, |
| 146 | NTooltip, |
| 147 | NUpload, |
| 148 | NUploadDragger, |
| 149 | useMessage |
| 150 | } from "naive-ui" |
| 151 | import { computed, onBeforeMount, ref, watch } from "vue" |
| 152 | import Api from "@/api" |
| 153 | import Icon from "@/components/common/Icon.vue" |
| 154 | import { useCaseReportTemplateStore } from "@/stores/caseReportTemplate" |
| 155 | |
| 156 | const UploadIcon = "carbon:cloud-upload" |
| 157 | const DownloadIcon = "carbon:document-download" |
| 158 | const DefaultTemplateIcon = "carbon:document-sketch" |
| 159 | const caseReportTemplateStore = useCaseReportTemplateStore() |
| 160 | const message = useMessage() |
| 161 | const loadingList = computed(() => caseReportTemplateStore.loading) |
| 162 | const uploading = ref(false) |
| 163 | const canceling = ref(false) |
| 164 | const checkingDefaultTemplate = ref(false) |
| 165 | const downloading = ref<false | string>(false) |
| 166 | const loading = computed(() => loadingList.value || uploading.value || canceling.value) |
| 167 | const showUploadForm = ref(false) |
| 168 | const templateNameList = computed(() => caseReportTemplateStore.templatesList) |
| 169 | const fileList = ref<UploadFileInfo[]>([]) |
| 170 | const newFile = computed<File | null>(() => fileList.value?.[0]?.file || null) |
| 171 | const isValid = computed(() => !!newFile.value) |
| 172 | const isDefaultTemplatePresent = ref(true) |
| 173 | |
| 174 | function openUploadForm() { |
| 175 | showUploadForm.value = true |
| 176 | } |
| 177 | |
| 178 | function closeUploadForm() { |
| 179 | showUploadForm.value = false |
| 180 | } |
| 181 | |
| 182 | function uploadCustomTemplate() { |
| 183 | if (!newFile.value) return |
| 184 | |
| 185 | uploading.value = true |
| 186 | |
| 187 | caseReportTemplateStore |
| 188 | .uploadCustomTemplate(newFile.value) |
| 189 | .then(res => { |
| 190 | if (res.data.success) { |
| 191 | message.success(res.data?.message || "Case Report Template uploaded successfully") |
| 192 | fileList.value = [] |
| 193 | } else { |
| 194 | message.warning(res.data?.message || "An error occurred. Please try again later.") |
| 195 | } |
| 196 | }) |
| 197 | .catch(err => { |
| 198 | message.error(err.response?.data?.message || "An error occurred. Please try again later.") |
| 199 | }) |
| 200 | .finally(() => { |
| 201 | uploading.value = false |
| 202 | }) |
| 203 | } |
| 204 | |
| 205 | function uploadDefaultTemplate() { |
| 206 | uploading.value = true |
| 207 | |
| 208 | Api.incidentManagement.cases |
| 209 | .uploadDefaultCaseReportTemplate() |
| 210 | .then(res => { |
| 211 | if (res.data.success) { |
| 212 | message.success(res.data?.message || "Case Report Template uploaded successfully") |
| 213 | refreshTemplates() |
| 214 | } else { |
| 215 | message.warning(res.data?.message || "An error occurred. Please try again later.") |
| 216 | } |
| 217 | }) |
| 218 | .catch(err => { |
| 219 | message.error(err.response?.data?.message || "An error occurred. Please try again later.") |
| 220 | }) |
| 221 | .finally(() => { |
| 222 | uploading.value = false |
| 223 | }) |
| 224 | } |
| 225 | |
| 226 | function checkDefaultCaseReportTemplateExists() { |
| 227 | checkingDefaultTemplate.value = true |
| 228 | |
| 229 | Api.incidentManagement.cases |
| 230 | .checkDefaultCaseReportTemplateExists() |
| 231 | .then(res => { |
| 232 | if (res.data.success) { |
| 233 | isDefaultTemplatePresent.value = res.data.default_template_exists |
| 234 | } else { |
| 235 | message.warning(res.data?.message || "An error occurred. Please try again later.") |
| 236 | } |
| 237 | }) |
| 238 | .catch(err => { |
| 239 | message.error(err.response?.data?.message || "An error occurred. Please try again later.") |
| 240 | }) |
| 241 | .finally(() => { |
| 242 | checkingDefaultTemplate.value = false |
| 243 | }) |
| 244 | } |
| 245 | |
| 246 | function downloadTemplate(templateName: string) { |
| 247 | downloading.value = templateName |
| 248 | |
| 249 | Api.incidentManagement.cases |
| 250 | .downloadCaseReportTemplate(templateName) |
| 251 | .then(res => { |
| 252 | if (res.data) { |
| 253 | saveAs(res.data, templateName) |
| 254 | } else { |
| 255 | message.warning("An error occurred. Please try again later.") |
| 256 | } |
| 257 | }) |
| 258 | .catch(err => { |
| 259 | message.error(err.response?.data?.message || "An error occurred. Please try again later.") |
| 260 | }) |
| 261 | .finally(() => { |
| 262 | downloading.value = false |
| 263 | }) |
| 264 | } |
| 265 | |
| 266 | function deleteTemplate(templateName: string) { |
| 267 | canceling.value = true |
| 268 | |
| 269 | caseReportTemplateStore |
| 270 | .deleteTemplate(templateName) |
| 271 | .then(res => { |
| 272 | if (res.data.success) { |
| 273 | message.success(res.data?.message || "Case Report Template deleted successfully") |
| 274 | } else { |
| 275 | message.warning(res.data?.message || "An error occurred. Please try again later.") |
| 276 | } |
| 277 | }) |
| 278 | .catch(err => { |
| 279 | message.error(err.response?.data?.message || "An error occurred. Please try again later.") |
| 280 | }) |
| 281 | .finally(() => { |
| 282 | canceling.value = false |
| 283 | }) |
| 284 | } |
| 285 | |
| 286 | function refreshTemplates() { |
| 287 | caseReportTemplateStore.refreshTemplates().catch(err => { |
| 288 | message.error(err.response?.data?.message || "An error occurred. Please try again later.") |
| 289 | }) |
| 290 | } |
| 291 | |
| 292 | watch(templateNameList, () => { |
| 293 | checkDefaultCaseReportTemplateExists() |
| 294 | }) |
| 295 | |
| 296 | onBeforeMount(() => { |
| 297 | refreshTemplates() |
| 298 | }) |
| 299 | </script> |