useTicketEdit.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. import { isEqualWith } from 'lodash-es'
  3. import { computed, reactive, watch } from 'vue'
  4. import { populateEditorNewLines } from '#shared/components/Form/fields/FieldEditor/utils.ts'
  5. import type {
  6. FormValues,
  7. FormRef,
  8. FormSubmitData,
  9. } from '#shared/components/Form/types.ts'
  10. import { getNodeByName } from '#shared/components/Form/utils.ts'
  11. import { useObjectAttributeFormData } from '#shared/entities/object-attributes/composables/useObjectAttributeFormData.ts'
  12. import { useObjectAttributes } from '#shared/entities/object-attributes/composables/useObjectAttributes.ts'
  13. import type { TicketById } from '#shared/entities/ticket/types.ts'
  14. import type { TicketArticleFormValues } from '#shared/entities/ticket-article/action/plugins/types.ts'
  15. import type { TicketUpdateInput } from '#shared/graphql/types.ts'
  16. import { EnumObjectManagerObjects } from '#shared/graphql/types.ts'
  17. import { MutationHandler } from '#shared/server/apollo/handler/index.ts'
  18. import type { PartialRequired } from '#shared/types/utils.ts'
  19. import { convertFilesToAttachmentInput } from '#shared/utils/files.ts'
  20. import { useTicketUpdateMutation } from '../graphql/mutations/update.api.ts'
  21. import type { ComputedRef, ShallowRef } from 'vue'
  22. type TicketArticleReceivedFormValues = PartialRequired<
  23. TicketArticleFormValues,
  24. // form always has these values
  25. 'articleType' | 'body' | 'internal'
  26. >
  27. export const useTicketEdit = (
  28. ticket: ComputedRef<TicketById | undefined>,
  29. form: ShallowRef<FormRef | undefined>,
  30. ) => {
  31. const initialTicketValue = reactive<FormValues>({})
  32. const mutationUpdate = new MutationHandler(useTicketUpdateMutation({}))
  33. watch(ticket, (newTicket, oldTicket) => {
  34. if (!newTicket) {
  35. return
  36. }
  37. // We need only to reset the form, when really something was changed (updatedAt is not relevant for the form).
  38. if (
  39. isEqualWith(newTicket, oldTicket, (value1, value2, key) => {
  40. if (key === 'updatedAt') return true
  41. })
  42. ) {
  43. return
  44. }
  45. const ticketId = initialTicketValue.id || newTicket.id
  46. const { internalId: ownerInternalId } = newTicket.owner
  47. initialTicketValue.id = newTicket.id
  48. // show Zammad user as empty
  49. initialTicketValue.owner_id = ownerInternalId === 1 ? null : ownerInternalId
  50. form.value?.resetForm(initialTicketValue, newTicket, {
  51. // don't reset to new values, if user changes something
  52. // if ticket is different, it's probably navigation to another ticket,
  53. // so we can safely reset the form
  54. // TODO: navigation to another ticket is currently always a re-render of the form, because of the component key(=newTicket.id) or?
  55. resetDirty: ticketId !== newTicket.id,
  56. })
  57. })
  58. const isTicketFormGroupValid = computed(() => {
  59. const ticketGroup = form.value?.formNode?.at('ticket')
  60. return !!ticketGroup?.context?.state.valid
  61. })
  62. const { attributesLookup: ticketObjectAttributesLookup } =
  63. useObjectAttributes(EnumObjectManagerObjects.Ticket)
  64. const processArticle = (
  65. formId: string,
  66. article: TicketArticleReceivedFormValues | undefined,
  67. ) => {
  68. if (!article) return null
  69. const contentType =
  70. getNodeByName(formId, 'body')?.context?.contentType || 'text/html'
  71. if (contentType === 'text/html') {
  72. article.body = populateEditorNewLines(article.body)
  73. }
  74. return {
  75. type: article.articleType,
  76. body: article.body,
  77. internal: article.internal,
  78. cc: article.cc,
  79. to: article.to,
  80. subject: article.subject,
  81. subtype: article.subtype,
  82. inReplyTo: article.inReplyTo,
  83. contentType,
  84. attachments: convertFilesToAttachmentInput(formId, article.attachments),
  85. security: article.security,
  86. }
  87. }
  88. const editTicket = async (formData: FormSubmitData) => {
  89. if (!ticket.value || !form.value) return undefined
  90. if (!formData.owner_id) {
  91. formData.owner_id = 1
  92. }
  93. const { internalObjectAttributeValues, additionalObjectAttributeValues } =
  94. useObjectAttributeFormData(ticketObjectAttributesLookup.value, formData)
  95. const formArticle = formData.article as
  96. | TicketArticleReceivedFormValues
  97. | undefined
  98. const article = processArticle(form.value.formId, formArticle)
  99. return mutationUpdate.send({
  100. ticketId: ticket.value.id,
  101. input: {
  102. ...internalObjectAttributeValues,
  103. objectAttributeValues: additionalObjectAttributeValues,
  104. article,
  105. } as TicketUpdateInput,
  106. })
  107. }
  108. return {
  109. initialTicketValue,
  110. isTicketFormGroupValid,
  111. editTicket,
  112. }
  113. }