main
vue 144 lines 3.73 KB
Raw
1 <template>
2 <CardEntity size="small" embedded>
3 <template #header-main>{{ comment.user_name }}</template>
4 <template #header-extra>{{ formatDate(comment.created_at, dFormats.datetime) }}</template>
5 <template #default>
6 <n-input
7 v-if="editMode"
8 v-model:value="newComment"
9 type="textarea"
10 :autosize="{ minRows: 3, maxRows: 10 }"
11 />
12 <div v-else v-html="commentHtml"></div>
13 </template>
14 <template #footer-extra>
15 <div v-if="editMode" class="flex items-center gap-2">
16 <n-button
17 size="tiny"
18 type="primary"
19 secondary
20 :focusable="false"
21 :disabled="!newComment?.trim()"
22 :loading="updating"
23 @click="updateComment"
24 >
25 <template #icon>
26 <Icon name="carbon:save" />
27 </template>
28 Save
29 </n-button>
30 <n-button size="tiny" :focusable="false" :disabled="updating" @click="setEditMode(false)">
31 <template #icon>
32 <Icon name="carbon:delete" />
33 </template>
34 Cancel
35 </n-button>
36 </div>
37 <div v-else class="flex items-center gap-2">
38 <n-button size="tiny" :focusable="false" :disabled="deleting" @click="setEditMode(true)">
39 <template #icon>
40 <Icon name="carbon:edit" />
41 </template>
42 Edit
43 </n-button>
44
45 <n-popconfirm to="body" @positive-click="deleteComment">
46 <template #trigger>
47 <n-button size="tiny" :focusable="false" :loading="deleting">
48 <template #icon>
49 <Icon name="carbon:trash-can" />
50 </template>
51 Delete
52 </n-button>
53 </template>
54 Are you sure you want to delete this comment?
55 </n-popconfirm>
56 </div>
57 </template>
58 </CardEntity>
59 </template>
60
61 <script setup lang="ts">
62 import type { CommentItem } from "@/types/comments"
63 import type { ApiError } from "@/types/common"
64 import { NButton, NInput, NPopconfirm, useMessage } from "naive-ui"
65 import { computed, ref } from "vue"
66 import Api from "@/api"
67 import CardEntity from "@/components/common/cards/CardEntity.vue"
68 import Icon from "@/components/common/Icon.vue"
69 import { useAuthStore } from "@/stores/auth"
70 import { useSettingsStore } from "@/stores/settings"
71 import { getApiErrorMessage } from "@/utils"
72 import { formatDate } from "@/utils/format"
73
74 const { comment, caseId } = defineProps<{
75 comment: CommentItem
76 caseId: number
77 }>()
78
79 const emit = defineEmits<{
80 (e: "updated", comment: CommentItem): void
81 (e: "deleted", commentId: number): void
82 }>()
83
84 const message = useMessage()
85 const authStore = useAuthStore()
86 const dFormats = useSettingsStore().dateFormat
87 const editMode = ref(false)
88 const newComment = ref<string | null>(null)
89 const updating = ref(false)
90 const deleting = ref(false)
91 const NEWLINE_REGEX = /\n/g
92
93 const commentHtml = computed(() => {
94 return comment.comment.replace(NEWLINE_REGEX, "<br>")
95 })
96
97 function setEditMode(mode: boolean) {
98 editMode.value = mode
99
100 if (mode) {
101 newComment.value = comment.comment
102 }
103 }
104
105 async function updateComment() {
106 if (!caseId || !newComment.value?.trim()) return
107
108 updating.value = true
109
110 try {
111 const response = await Api.cases.updateComment({
112 caseId,
113 commentId: comment.id,
114 comment: newComment.value.trim(),
115 userName: authStore.userName || ""
116 })
117
118 emit("updated", response.data.comment)
119 newComment.value = ""
120 message.success(response.data?.message || "Comment updated successfully")
121 setEditMode(false)
122 } catch (err) {
123 message.error(getApiErrorMessage(err as ApiError))
124 } finally {
125 updating.value = false
126 }
127 }
128
129 async function deleteComment() {
130 if (!comment) return
131
132 deleting.value = true
133
134 try {
135 const response = await Api.cases.deleteComment(comment.id)
136 emit("deleted", comment.id)
137 message.success(response.data?.message || "Comment deleted successfully")
138 } catch (err) {
139 message.error(getApiErrorMessage(err as ApiError))
140 } finally {
141 deleting.value = false
142 }
143 }
144 </script>