TicketSharedDraftFlyout.vue 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. <!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { computed, toRef } from 'vue'
  4. import {
  5. NotificationTypes,
  6. useNotifications,
  7. } from '#shared/components/CommonNotifications/index.ts'
  8. import CommonUserAvatar from '#shared/components/CommonUserAvatar/CommonUserAvatar.vue'
  9. import type { FormRef } from '#shared/components/Form/types.ts'
  10. import { useForm } from '#shared/components/Form/useForm.ts'
  11. import { useConfirmation } from '#shared/composables/useConfirmation.ts'
  12. import type {
  13. TicketSharedDraftStartSingleQuery,
  14. TicketSharedDraftZoomShowQuery,
  15. } from '#shared/graphql/types.ts'
  16. import { convertToGraphQLId } from '#shared/graphql/utils.ts'
  17. import {
  18. MutationHandler,
  19. QueryHandler,
  20. } from '#shared/server/apollo/handler/index.ts'
  21. import type {
  22. OperationMutationFunction,
  23. OperationQueryFunction,
  24. } from '#shared/types/server/apollo/handler'
  25. import CommonButton from '#desktop/components/CommonButton/CommonButton.vue'
  26. import CommonFlyout from '#desktop/components/CommonFlyout/CommonFlyout.vue'
  27. import { closeFlyout } from '#desktop/components/CommonFlyout/useFlyout.ts'
  28. import CommonObjectAttribute from '#desktop/components/CommonObjectAttribute/CommonObjectAttribute.vue'
  29. import CommonObjectAttributeContainer from '#desktop/components/CommonObjectAttribute/CommonObjectAttributeContainer.vue'
  30. const props = defineProps<{
  31. sharedDraftId: string
  32. form: FormRef | undefined
  33. draftType: 'start' | 'detail-view'
  34. metaInformationQuery: OperationQueryFunction
  35. deleteMutation: OperationMutationFunction
  36. }>()
  37. const emit = defineEmits<{
  38. 'shared-draft-applied': [id: string]
  39. 'shared-draft-deleted': [id: string]
  40. }>()
  41. const { metaInformationQuery, deleteMutation } = props
  42. const metaInformationQueryHandler = new QueryHandler(
  43. metaInformationQuery({
  44. sharedDraftId: props.sharedDraftId,
  45. }),
  46. )
  47. const metaInformationQueryResult = metaInformationQueryHandler.result()
  48. const sharedDraft = computed(() => {
  49. if (props.draftType === 'start') {
  50. return metaInformationQueryResult.value
  51. ?.ticketSharedDraftStartSingle as TicketSharedDraftStartSingleQuery['ticketSharedDraftStartSingle']
  52. }
  53. return metaInformationQueryResult.value
  54. ?.ticketSharedDraftZoomShow as TicketSharedDraftZoomShowQuery['ticketSharedDraftZoomShow']
  55. })
  56. const sharedDraftContent = computed(() => {
  57. if (props.draftType === 'start') {
  58. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  59. const content: any =
  60. sharedDraft.value as TicketSharedDraftStartSingleQuery['ticketSharedDraftStartSingle']
  61. return content.content.body
  62. }
  63. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  64. const newArticle: any =
  65. sharedDraft.value as TicketSharedDraftZoomShowQuery['ticketSharedDraftZoomShow']
  66. return newArticle.newArticle.body
  67. })
  68. const close = () => {
  69. closeFlyout('shared-draft')
  70. }
  71. const { waitForConfirmation, waitForVariantConfirmation } = useConfirmation()
  72. const { notify } = useNotifications()
  73. const sharedDrafteleteMutation = new MutationHandler(deleteMutation({}))
  74. const { isDirty, triggerFormUpdater, updateFieldValues, values } = useForm(
  75. toRef(props, 'form'),
  76. )
  77. const deleteSharedDraft = async (sharedDraftId: string) => {
  78. const confirmed = await waitForVariantConfirmation('delete')
  79. if (!confirmed) return
  80. sharedDrafteleteMutation
  81. .send({
  82. sharedDraftId,
  83. })
  84. .then(() => {
  85. // Reset shared draft internal ID, if currently set to the same value in the form.
  86. if (
  87. (props.draftType === 'start' &&
  88. convertToGraphQLId(
  89. 'Ticket::SharedDraftStart',
  90. Number(values.value.shared_draft_id),
  91. ) === sharedDraftId) ||
  92. (props.draftType === 'detail-view' &&
  93. convertToGraphQLId(
  94. 'Ticket::SharedDraftZoom',
  95. Number(values.value.shared_draft_id),
  96. ) === sharedDraftId)
  97. ) {
  98. updateFieldValues({
  99. shared_draft_id: null,
  100. })
  101. }
  102. notify({
  103. id: 'shared-draft-deleted',
  104. type: NotificationTypes.Success,
  105. message: __('Shared draft has been deleted.'),
  106. })
  107. emit('shared-draft-deleted', sharedDraftId)
  108. close()
  109. })
  110. }
  111. const applySharedDraft = async (sharedDraftId: string) => {
  112. if (isDirty.value) {
  113. const confirmed = await waitForConfirmation(
  114. __('There is existing content. Do you want to overwrite it?'),
  115. {
  116. headerTitle: __('Apply Draft'),
  117. buttonLabel: __('Overwrite Content'),
  118. buttonVariant: 'danger',
  119. },
  120. )
  121. if (!confirmed) return
  122. }
  123. const additionalParams = {
  124. sharedDraftId,
  125. draftType: props.draftType,
  126. }
  127. triggerFormUpdater({ additionalParams })
  128. // NB: Skip notifying the user via toast, since they will immediately see the shared draft applied on screen.
  129. emit('shared-draft-applied', sharedDraftId)
  130. close()
  131. }
  132. const headerTitle = computed(() => {
  133. if (props.draftType === 'start') {
  134. return __('Preview Shared Draft')
  135. }
  136. return __('Apply Shared Draft')
  137. })
  138. </script>
  139. <template>
  140. <CommonFlyout
  141. :header-title="headerTitle"
  142. :footer-action-options="{
  143. actionLabel: __('Apply'),
  144. actionButton: { variant: 'primary' },
  145. }"
  146. header-icon="file-text"
  147. name="shared-draft"
  148. >
  149. <div v-if="sharedDraft" class="flex flex-col gap-3">
  150. <!-- TODO: Surely we should also display the name of the shared draft somewhere, no? -->
  151. <!-- <CommonObjectAttributeContainer>
  152. <CommonObjectAttribute :label="__('Name')">
  153. {{ sharedDraft?.name }}
  154. </CommonObjectAttribute>
  155. </CommonObjectAttributeContainer> -->
  156. <div class="flex items-start gap-y-3">
  157. <CommonObjectAttributeContainer
  158. v-if="sharedDraft?.updatedBy"
  159. class="grow"
  160. >
  161. <CommonObjectAttribute :label="__('Author')">
  162. <div class="flex items-center gap-1.5">
  163. <CommonUserAvatar :entity="sharedDraft?.updatedBy" size="small" />
  164. <CommonLabel>{{ sharedDraft.updatedBy.fullname }}</CommonLabel>
  165. </div>
  166. </CommonObjectAttribute>
  167. </CommonObjectAttributeContainer>
  168. <CommonObjectAttributeContainer class="grow">
  169. <CommonObjectAttribute :label="__('Last changed')">
  170. <CommonDateTime :date-time="sharedDraft?.updatedAt" />
  171. </CommonObjectAttribute>
  172. </CommonObjectAttributeContainer>
  173. </div>
  174. <!--
  175. TODO: Think about showing more attributes here, since the body might not be present at all in the draft.
  176. But keep in mind this might not be easily possible, since we are missing some information from the query.
  177. For example, we have only `owner_id`/`state_id`/`priority_id`, what about lookup objects?!
  178. -->
  179. <CommonObjectAttributeContainer v-if="sharedDraftContent">
  180. <CommonObjectAttribute :label="__('Text')">
  181. <!-- eslint-disable vue/no-v-html -->
  182. <span v-html="sharedDraftContent" />
  183. </CommonObjectAttribute>
  184. </CommonObjectAttributeContainer>
  185. </div>
  186. <template #footer>
  187. <div class="flex items-center justify-end gap-4">
  188. <CommonButton size="large" variant="secondary" @click="close">
  189. {{ $t('Cancel & Go Back') }}
  190. </CommonButton>
  191. <CommonButton
  192. size="large"
  193. variant="danger"
  194. @click="deleteSharedDraft(sharedDraftId)"
  195. >
  196. {{ $t('Delete') }}
  197. </CommonButton>
  198. <CommonButton
  199. size="large"
  200. variant="primary"
  201. @click="applySharedDraft(sharedDraftId)"
  202. >
  203. {{ $t('Apply') }}
  204. </CommonButton>
  205. </div>
  206. </template>
  207. </CommonFlyout>
  208. </template>