TicketLinksFlyout.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  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 Form from '#shared/components/Form/Form.vue'
  9. import type { FormSubmitData } from '#shared/components/Form/types.ts'
  10. import { useForm } from '#shared/components/Form/useForm.ts'
  11. import type { TicketById } from '#shared/entities/ticket/types.ts'
  12. import { EnumLinkType, type LinkListQuery } from '#shared/graphql/types.ts'
  13. import { MutationHandler } from '#shared/server/apollo/handler/index.ts'
  14. import CommonFlyout from '#desktop/components/CommonFlyout/CommonFlyout.vue'
  15. import type { ActionFooterOptions } from '#desktop/components/CommonFlyout/types.ts'
  16. import { closeFlyout } from '#desktop/components/CommonFlyout/useFlyout.ts'
  17. import TicketRelationAndRecentLists from '#desktop/pages/ticket/components/TicketDetailView/TicketRelationAndRecentLists/TicketRelationAndRecentLists.vue'
  18. import { useObjectLinkTypes } from '#desktop/pages/ticket/composables/useObjectLinkTypes.ts'
  19. import { useTargetTicketOptions } from '#desktop/pages/ticket/composables/useTargetTicketOptions.ts'
  20. import { useLinkAddMutation } from '#desktop/pages/ticket/graphql/mutations/linkAdd.api.ts'
  21. import { LinkListDocument } from '#desktop/pages/ticket/graphql/queries/linkList.api.ts'
  22. import type { Ref } from 'vue'
  23. interface Props {
  24. sourceTicket: Ref<TicketById>
  25. }
  26. const props = defineProps<Props>()
  27. const sourceTicket = toRef(props, 'sourceTicket')
  28. const { form, updateFieldValues, onChangedField } = useForm()
  29. const { formListTargetTicketOptions, targetTicketId, handleTicketClick } =
  30. useTargetTicketOptions(onChangedField, updateFieldValues)
  31. const { linkTypes } = useObjectLinkTypes()
  32. const linkFormSchema = [
  33. {
  34. isLayout: true,
  35. element: 'div',
  36. attrs: {
  37. class: 'grid gap-y-2.5 gap-x-3',
  38. },
  39. children: [
  40. {
  41. name: 'targetTicketId',
  42. type: 'ticket',
  43. label: __('Link ticket'),
  44. exceptTicketInternalId: sourceTicket.value.internalId,
  45. options: formListTargetTicketOptions,
  46. required: true,
  47. },
  48. {
  49. name: 'linkType',
  50. type: 'select',
  51. label: __('Link type'),
  52. options: linkTypes,
  53. },
  54. ],
  55. },
  56. ]
  57. const initialValues = {
  58. linkType: EnumLinkType.Normal,
  59. }
  60. const footerActionOptions = computed<ActionFooterOptions>(() => ({
  61. actionButton: {
  62. variant: 'submit',
  63. type: 'submit',
  64. },
  65. actionLabel: __('Link'),
  66. form: form.value,
  67. }))
  68. const { notify } = useNotifications()
  69. const addLink = async (
  70. formData: FormSubmitData<{
  71. targetTicketId: string
  72. linkType: EnumLinkType
  73. }>,
  74. ) => {
  75. const addLinkMutation = new MutationHandler(
  76. useLinkAddMutation({
  77. variables: {
  78. input: {
  79. // Don't ask me why, but the sourceId and targetId are swapped to be consistent with the old UI.
  80. sourceId: formData.targetTicketId,
  81. targetId: sourceTicket.value.id,
  82. type: formData.linkType,
  83. },
  84. },
  85. update: (cache, { data }) => {
  86. if (!data) return
  87. const { linkAdd } = data
  88. if (!linkAdd?.link) return
  89. const { link: newLink } = linkAdd
  90. const variables = {
  91. objectId: sourceTicket.value.id,
  92. targetType: 'Ticket',
  93. }
  94. let existingLinks = cache.readQuery<LinkListQuery>({
  95. query: LinkListDocument,
  96. variables,
  97. })
  98. const newIdPresent = existingLinks?.linkList?.find((link) => {
  99. return link.item.id === newLink.item.id && link.type === newLink.type
  100. })
  101. if (newIdPresent) return
  102. existingLinks = {
  103. ...existingLinks,
  104. linkList: [...(existingLinks?.linkList || []), newLink],
  105. }
  106. cache.writeQuery({
  107. query: LinkListDocument,
  108. data: existingLinks,
  109. variables,
  110. })
  111. },
  112. }),
  113. {
  114. errorShowNotification: false,
  115. },
  116. )
  117. return addLinkMutation.send().then((data) => {
  118. if (data?.linkAdd) {
  119. return () => {
  120. notify({
  121. type: NotificationTypes.Success,
  122. message: __('Link added successfully'),
  123. })
  124. closeFlyout('ticket-link')
  125. }
  126. }
  127. })
  128. }
  129. </script>
  130. <template>
  131. <CommonFlyout
  132. :header-title="__('Link Tickets')"
  133. header-icon="link"
  134. name="ticket-link"
  135. size="large"
  136. no-close-on-action
  137. :footer-action-options="footerActionOptions"
  138. >
  139. <div class="space-y-6">
  140. <Form
  141. ref="form"
  142. :schema="linkFormSchema"
  143. :initial-values="initialValues"
  144. should-autofocus
  145. @submit="
  146. addLink(
  147. $event as FormSubmitData<{
  148. targetTicketId: string
  149. linkType: EnumLinkType
  150. }>,
  151. )
  152. "
  153. />
  154. <TicketRelationAndRecentLists
  155. :customer-id="sourceTicket.customer.id"
  156. :internal-ticket-id="sourceTicket.internalId"
  157. :selected-ticket-id="targetTicketId"
  158. @click-ticket="handleTicketClick"
  159. />
  160. </div>
  161. </CommonFlyout>
  162. </template>