| 1 | <template> |
| 2 | <n-button :size type="primary" :loading="exporting" secondary @click="showForm = true"> |
| 3 | <template #icon> |
| 4 | <Icon :name="ReportIcon" /> |
| 5 | </template> |
| 6 | Generate Report |
| 7 | </n-button> |
| 8 | |
| 9 | <n-modal |
| 10 | v-model:show="showForm" |
| 11 | display-directive="show" |
| 12 | preset="card" |
| 13 | :style="{ maxWidth: 'min(600px, 90vw)', minHeight: 'min(240px, 90vh)', overflow: 'hidden' }" |
| 14 | title="Generate Report" |
| 15 | :bordered="false" |
| 16 | segmented |
| 17 | > |
| 18 | <n-spin :show="exporting" class="creation-report-form"> |
| 19 | <n-form ref="formRef" :model="form" :rules> |
| 20 | <div class="flex flex-col gap-2"> |
| 21 | <n-form-item label="Template" path="template_name"> |
| 22 | <CaseReportTemplateSelect v-model:value="form.template_name" /> |
| 23 | </n-form-item> |
| 24 | <n-collapse-transition :show="!!form.template_name"> |
| 25 | <n-form-item label="Filename" path="file_name"> |
| 26 | <n-input-group> |
| 27 | <n-input |
| 28 | v-model:value.trim="form.file_name" |
| 29 | placeholder="Please insert File Name" |
| 30 | clearable |
| 31 | /> |
| 32 | <n-input-group-label v-if="reportType">.{{ reportType }}</n-input-group-label> |
| 33 | </n-input-group> |
| 34 | </n-form-item> |
| 35 | </n-collapse-transition> |
| 36 | |
| 37 | <div class="mt-3 flex justify-between gap-4"> |
| 38 | <n-button :disabled="exporting" @click="reset()">Reset</n-button> |
| 39 | <n-button |
| 40 | type="primary" |
| 41 | :disabled="!isValid" |
| 42 | :loading="exporting" |
| 43 | @click="validate(() => exportCases())" |
| 44 | > |
| 45 | Generate |
| 46 | </n-button> |
| 47 | </div> |
| 48 | </div> |
| 49 | </n-form> |
| 50 | </n-spin> |
| 51 | </n-modal> |
| 52 | </template> |
| 53 | |
| 54 | <script setup lang="ts"> |
| 55 | import type { ButtonSize, FormInst, FormRules, FormValidationError } from "naive-ui" |
| 56 | import type { CaseReportPayload } from "@/api/endpoints/incidentManagement/cases" |
| 57 | import type { DeepNullable } from "@/types/common" |
| 58 | import { saveAs } from "file-saver" |
| 59 | import { |
| 60 | NButton, |
| 61 | NCollapseTransition, |
| 62 | NForm, |
| 63 | NFormItem, |
| 64 | NInput, |
| 65 | NInputGroup, |
| 66 | NInputGroupLabel, |
| 67 | NModal, |
| 68 | NSpin, |
| 69 | useMessage |
| 70 | } from "naive-ui" |
| 71 | import { computed, ref } from "vue" |
| 72 | import Api from "@/api" |
| 73 | import Icon from "@/components/common/Icon.vue" |
| 74 | import { useSettingsStore } from "@/stores/settings" |
| 75 | import { formatDate } from "@/utils/format" |
| 76 | import CaseReportTemplateSelect from "./CaseReportTemplateSelect.vue" |
| 77 | |
| 78 | const { size, caseId } = defineProps<{ size?: ButtonSize; caseId: number }>() |
| 79 | |
| 80 | const ReportIcon = "carbon:report-data" |
| 81 | const dFormats = useSettingsStore().dateFormat |
| 82 | const exporting = ref(false) |
| 83 | const showForm = ref(false) |
| 84 | const message = useMessage() |
| 85 | const form = ref<DeepNullable<CaseReportPayload>>(getClearForm()) |
| 86 | const formRef = ref<FormInst | null>(null) |
| 87 | const reportType = computed<"docx" | "pdf" | null>(() => { |
| 88 | const ext = form.value.template_name?.split(".").pop()?.toLowerCase() || null |
| 89 | switch (ext) { |
| 90 | case "docx": |
| 91 | return "docx" |
| 92 | case "html": |
| 93 | return "pdf" |
| 94 | default: |
| 95 | return null |
| 96 | } |
| 97 | }) |
| 98 | |
| 99 | const rules: FormRules = { |
| 100 | template_name: { |
| 101 | required: true, |
| 102 | message: "Please input the Report Template", |
| 103 | trigger: ["input", "blur"] |
| 104 | }, |
| 105 | file_name: { |
| 106 | required: true, |
| 107 | message: "Please input the Report Filename", |
| 108 | trigger: ["input", "blur"] |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | const isValid = computed(() => { |
| 113 | if (!form.value.template_name) { |
| 114 | return false |
| 115 | } |
| 116 | if (!form.value.file_name) { |
| 117 | return false |
| 118 | } |
| 119 | |
| 120 | return true |
| 121 | }) |
| 122 | |
| 123 | function validate(cb?: () => void) { |
| 124 | if (!formRef.value) return |
| 125 | |
| 126 | formRef.value.validate((errors?: Array<FormValidationError>) => { |
| 127 | if (!errors) { |
| 128 | if (cb) cb() |
| 129 | } else { |
| 130 | message.warning("You must fill in the required fields correctly.") |
| 131 | return false |
| 132 | } |
| 133 | }) |
| 134 | } |
| 135 | |
| 136 | function getClearForm(): DeepNullable<CaseReportPayload> { |
| 137 | return { |
| 138 | case_id: caseId, |
| 139 | file_name: null, |
| 140 | template_name: null |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | function reset(force?: boolean) { |
| 145 | if (!exporting.value || force) { |
| 146 | resetForm() |
| 147 | formRef.value?.restoreValidation() |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | function resetForm() { |
| 152 | form.value = getClearForm() |
| 153 | } |
| 154 | |
| 155 | function exportCases() { |
| 156 | if (!form.value.file_name || !form.value.template_name || !reportType.value) return |
| 157 | |
| 158 | exporting.value = true |
| 159 | |
| 160 | const extension = reportType.value === "pdf" ? "pdf" : "docx" |
| 161 | const mimeType = |
| 162 | reportType.value === "pdf" |
| 163 | ? "application/pdf" |
| 164 | : "application/vnd.openxmlformats-officedocument.wordprocessingml.document" |
| 165 | |
| 166 | const fileName = form.value.file_name |
| 167 | ? `${form.value.file_name}.${extension}` |
| 168 | : `case:${caseId}_report_${formatDate(new Date(), dFormats.datetimesec)}.${extension}` |
| 169 | |
| 170 | Api.incidentManagement.cases |
| 171 | .generateCaseReport( |
| 172 | { |
| 173 | case_id: caseId, |
| 174 | file_name: form.value.file_name, |
| 175 | template_name: form.value.template_name |
| 176 | }, |
| 177 | reportType.value |
| 178 | ) |
| 179 | .then(res => { |
| 180 | if (res.data) { |
| 181 | saveAs( |
| 182 | new Blob([res.data], { |
| 183 | type: mimeType |
| 184 | }), |
| 185 | fileName |
| 186 | ) |
| 187 | reset(true) |
| 188 | } else { |
| 189 | message.warning("An error occurred. Please try again later.") |
| 190 | } |
| 191 | }) |
| 192 | .catch(err => { |
| 193 | message.error(err.response?.data?.message || "An error occurred. Please try again later.") |
| 194 | }) |
| 195 | .finally(() => { |
| 196 | exporting.value = false |
| 197 | }) |
| 198 | } |
| 199 | </script> |