TicketLinksFlyout.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  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. clearable: true,
  47. required: true,
  48. },
  49. {
  50. name: 'linkType',
  51. type: 'select',
  52. label: __('Link type'),
  53. options: linkTypes,
  54. },
  55. ],
  56. },
  57. ]
  58. const initialValues = {
  59. linkType: EnumLinkType.Normal,
  60. }
  61. const footerActionOptions = computed<ActionFooterOptions>(() => ({
  62. actionButton: {
  63. variant: 'submit',
  64. type: 'submit',
  65. },
  66. actionLabel: __('Link'),
  67. form: form.value,
  68. }))
  69. const { notify } = useNotifications()
  70. const addLink = async (
  71. formData: FormSubmitData<{
  72. targetTicketId: string
  73. linkType: EnumLinkType
  74. }>,
  75. ) => {
  76. const addLinkMutation = new MutationHandler(
  77. useLinkAddMutation({
  78. variables: {
  79. input: {
  80. // Don't ask me why, but the sourceId and targetId are swapped to be consistent with the old UI.
  81. sourceId: formData.targetTicketId,
  82. targetId: sourceTicket.value.id,
  83. type: formData.linkType,
  84. },
  85. },
  86. update: (cache, { data }) => {
  87. if (!data) return
  88. const { linkAdd } = data
  89. if (!linkAdd?.link) return
  90. const { link: newLink } = linkAdd
  91. const variables = {
  92. objectId: sourceTicket.value.id,
  93. targetType: 'Ticket',
  94. }
  95. let existingLinks = cache.readQuery<LinkListQuery>({
  96. query: LinkListDocument,
  97. variables,
  98. })
  99. const newIdPresent = existingLinks?.linkList?.find((link) => {
  100. return link.item.id === newLink.item.id && link.type === newLink.type
  101. })
  102. if (newIdPresent) return
  103. existingLinks = {
  104. ...existingLinks,
  105. linkList: [...(existingLinks?.linkList || []), newLink],
  106. }
  107. cache.writeQuery({
  108. query: LinkListDocument,
  109. data: existingLinks,
  110. variables,
  111. })
  112. },
  113. }),
  114. {
  115. errorShowNotification: false,
  116. },
  117. )
  118. return addLinkMutation.send().then((data) => {
  119. if (data?.linkAdd) {
  120. return () => {
  121. notify({
  122. type: NotificationTypes.Success,
  123. message: __('Link added successfully'),
  124. })
  125. closeFlyout('ticket-link')
  126. }
  127. }
  128. })
  129. }
  130. </script>
  131. <template>
  132. <CommonFlyout
  133. :header-title="__('Link Tickets')"
  134. header-icon="link"
  135. name="ticket-link"
  136. size="large"
  137. no-close-on-action
  138. :footer-action-options="footerActionOptions"
  139. >
  140. <div class="space-y-6">
  141. <Form
  142. ref="form"
  143. :schema="linkFormSchema"
  144. :initial-values="initialValues"
  145. should-autofocus
  146. @submit="
  147. addLink(
  148. $event as FormSubmitData<{
  149. targetTicketId: string
  150. linkType: EnumLinkType
  151. }>,
  152. )
  153. "
  154. />
  155. <TicketRelationAndRecentLists
  156. :customer-id="sourceTicket.customer.id"
  157. :internal-ticket-id="sourceTicket.internalId"
  158. :selected-ticket-id="targetTicketId"
  159. @click-ticket="handleTicketClick"
  160. />
  161. </div>
  162. </CommonFlyout>
  163. </template>