ArticleReplyDialog.vue 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. <!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { cloneDeep, isEqual } from 'lodash-es'
  4. import { computed, onMounted, onUnmounted } from 'vue'
  5. import type { FormRef } from '#shared/components/Form/types.ts'
  6. import { useConfirmation } from '#shared/composables/useConfirmation.ts'
  7. import type { TicketById } from '#shared/entities/ticket/types.ts'
  8. import CommonButton from '#mobile/components/CommonButton/CommonButton.vue'
  9. import CommonDialog from '#mobile/components/CommonDialog/CommonDialog.vue'
  10. import { closeDialog } from '#mobile/composables/useDialog.ts'
  11. import type { FormKitNode } from '@formkit/core'
  12. import type { ShallowRef } from 'vue'
  13. interface Props {
  14. name: string
  15. ticket: TicketById
  16. articleFormGroupNode?: FormKitNode
  17. newTicketArticlePresent: boolean
  18. needSpaceForSaveBanner: boolean
  19. form: ShallowRef<FormRef | undefined>
  20. }
  21. const props = defineProps<Props>()
  22. const emit = defineEmits<{
  23. 'show-article-form': []
  24. 'hide-article-form': []
  25. discard: []
  26. done: []
  27. }>()
  28. const label = computed(() =>
  29. props.newTicketArticlePresent ? __('Edit reply') : __('Add reply'),
  30. )
  31. const articleFormGroupNodeContext = computed(
  32. () => props.articleFormGroupNode?.context,
  33. )
  34. const rememberArticleFormData = cloneDeep({
  35. ...articleFormGroupNodeContext.value?._value,
  36. __init: true,
  37. })
  38. const dialogFormIsDirty = computed(() => {
  39. if (!props.newTicketArticlePresent)
  40. return !!articleFormGroupNodeContext.value?.state.dirty
  41. return !isEqual(
  42. rememberArticleFormData,
  43. articleFormGroupNodeContext.value?._value,
  44. )
  45. })
  46. const { waitForConfirmation } = useConfirmation()
  47. const cancelDialog = async () => {
  48. if (dialogFormIsDirty.value) {
  49. const confirmed = await waitForConfirmation(
  50. __('Are you sure? You have changes that will get lost.'),
  51. {
  52. buttonLabel: __('Discard changes'),
  53. buttonVariant: 'danger',
  54. },
  55. )
  56. if (!confirmed) return
  57. }
  58. // Set article form data back to the remembered state.
  59. // For the first time we need to do nothing, because the article
  60. // group will be removed again from the form.
  61. if (props.newTicketArticlePresent) {
  62. props.articleFormGroupNode?.input(rememberArticleFormData)
  63. }
  64. closeDialog(props.name)
  65. }
  66. const discardDialog = async () => {
  67. const confirmed = await waitForConfirmation(
  68. __('Are you sure? The prepared article will be removed.'),
  69. {
  70. buttonLabel: __('Discard article'),
  71. buttonVariant: 'danger',
  72. },
  73. )
  74. if (!confirmed) return
  75. // Reset only the article group.
  76. props.articleFormGroupNode?.reset()
  77. emit('discard')
  78. closeDialog(props.name)
  79. }
  80. onMounted(() => {
  81. emit('show-article-form')
  82. })
  83. onUnmounted(() => {
  84. emit('hide-article-form')
  85. })
  86. const close = () => {
  87. emit('done')
  88. closeDialog(props.name)
  89. }
  90. </script>
  91. <template>
  92. <CommonDialog class="w-full" :name="name" :label="label">
  93. <template #before-label>
  94. <CommonButton transparent-background @click="cancelDialog">
  95. {{ $t('Cancel') }}
  96. </CommonButton>
  97. </template>
  98. <template #after-label>
  99. <CommonButton
  100. variant="primary"
  101. :disabled="!dialogFormIsDirty"
  102. transparent-background
  103. @pointerdown.stop
  104. @click="close()"
  105. @keypress.space.prevent="close()"
  106. >
  107. {{ $t('Done') }}
  108. </CommonButton>
  109. </template>
  110. <div class="w-full p-4">
  111. <div data-ticket-article-reply-form />
  112. <FormKit
  113. v-if="newTicketArticlePresent"
  114. variant="danger"
  115. wrapper-class="mt-4 flex grow justify-center items-center"
  116. input-class="py-2 px-4 w-full h-14 rounded-xl select-none"
  117. name="discardArticle"
  118. type="button"
  119. @click="discardDialog"
  120. >
  121. {{ $t('Discard your unsaved changes') }}
  122. </FormKit>
  123. <div
  124. class="transition-all"
  125. :class="{ 'pb-16': needSpaceForSaveBanner }"
  126. ></div>
  127. </div>
  128. </CommonDialog>
  129. </template>