PersonalSettingNotifications.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. <!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { isEqual } from 'lodash-es'
  4. import { storeToRefs } from 'pinia'
  5. import { computed, ref, watch } from 'vue'
  6. import {
  7. NotificationTypes,
  8. useNotifications,
  9. } from '#shared/components/CommonNotifications/index.ts'
  10. import Form from '#shared/components/Form/Form.vue'
  11. import { type FormSubmitData } from '#shared/components/Form/types.ts'
  12. import { useForm } from '#shared/components/Form/useForm.ts'
  13. import { useConfirmation } from '#shared/composables/useConfirmation.ts'
  14. import { defineFormSchema } from '#shared/form/defineFormSchema.ts'
  15. import {
  16. EnumFormUpdaterId,
  17. EnumNotificationSoundFile,
  18. type UserNotificationMatrixInput,
  19. } from '#shared/graphql/types.ts'
  20. import { convertToGraphQLId } from '#shared/graphql/utils.ts'
  21. import { MutationHandler } from '#shared/server/apollo/handler/index.ts'
  22. import { useSessionStore } from '#shared/stores/session.ts'
  23. import type { UserData } from '#shared/types/store.ts'
  24. import CommonButton from '#desktop/components/CommonButton/CommonButton.vue'
  25. import LayoutContent from '#desktop/components/layout/LayoutContent.vue'
  26. import { useBreadcrumb } from '#desktop/pages/personal-setting/composables/useBreadcrumb.ts'
  27. import { useUserCurrentNotificationPreferencesResetMutation } from '#desktop/pages/personal-setting/graphql/mutations/userCurrentNotificationPreferencesReset.api.ts'
  28. import { useUserCurrentNotificationPreferencesUpdateMutation } from '#desktop/pages/personal-setting/graphql/mutations/userCurrentNotificationPreferencesUpdate.api.ts'
  29. import type { NotificationFormData } from '#desktop/pages/personal-setting/types/notifications.ts'
  30. const { breadcrumbItems } = useBreadcrumb(__('Notifications'))
  31. const { user } = storeToRefs(useSessionStore())
  32. const { notify } = useNotifications()
  33. const { waitForConfirmation } = useConfirmation()
  34. const loading = ref(false)
  35. const { form, onChangedField, formReset, values, isDirty } = useForm()
  36. const soundOptions = Object.keys(EnumNotificationSoundFile).map((sound) => ({
  37. label: sound,
  38. value: sound,
  39. }))
  40. const schema = defineFormSchema([
  41. {
  42. type: 'notifications',
  43. name: 'matrix',
  44. label: __('Notification matrix'),
  45. labelSrOnly: true,
  46. },
  47. {
  48. type: 'select',
  49. name: 'group_ids',
  50. label: __('Limit notifications to specific groups'),
  51. help: __('Affects only notifications for not assigned and all tickets.'),
  52. props: {
  53. clearable: true,
  54. multiple: true,
  55. noOptionsLabelTranslation: true,
  56. },
  57. },
  58. {
  59. type: 'select',
  60. name: 'file',
  61. label: __('Notification sound'),
  62. props: {
  63. options: soundOptions,
  64. },
  65. },
  66. {
  67. type: 'toggle',
  68. name: 'enabled',
  69. label: __('Play user interface sound effects'),
  70. props: {
  71. variants: { true: 'True', false: 'False' },
  72. },
  73. },
  74. ])
  75. const initialFormValues = computed<NotificationFormData>((oldValues) => {
  76. const { notificationConfig = {}, notificationSound = {} } =
  77. user.value?.personalSettings || {}
  78. const values: NotificationFormData = {
  79. group_ids: notificationConfig?.groupIds ?? [],
  80. matrix: notificationConfig?.matrix || {},
  81. // Default notification sound settings are not present on the user preferences.
  82. file: notificationSound?.file ?? EnumNotificationSoundFile.Xylo,
  83. enabled: notificationSound?.enabled ?? true,
  84. }
  85. if (oldValues && isEqual(values, oldValues)) return oldValues
  86. return values
  87. })
  88. watch(initialFormValues, (newValues) => {
  89. // No reset needed when the form has already the correct state.
  90. if (isEqual(values.value, newValues) && !isDirty.value) return
  91. formReset({ values: newValues })
  92. })
  93. onChangedField('file', (fileName) => {
  94. new Audio(`/assets/sounds/${fileName?.toString()}.mp3`)?.play()
  95. })
  96. const onSubmit = async (form: FormSubmitData<NotificationFormData>) => {
  97. loading.value = true
  98. const notificationUpdateMutation = new MutationHandler(
  99. useUserCurrentNotificationPreferencesUpdateMutation(),
  100. {
  101. errorNotificationMessage: __('Notification settings could not be saved.'),
  102. },
  103. )
  104. return notificationUpdateMutation
  105. .send({
  106. matrix: form.matrix as UserNotificationMatrixInput,
  107. groupIds:
  108. form?.group_ids?.map((id) => convertToGraphQLId('Group', id)) || [],
  109. sound: {
  110. file: form.file as EnumNotificationSoundFile,
  111. enabled: form.enabled,
  112. },
  113. })
  114. .then((response) => {
  115. if (response?.userCurrentNotificationPreferencesUpdate) {
  116. notify({
  117. id: 'notification-update-success',
  118. type: NotificationTypes.Success,
  119. message: __('Notification settings have been saved successfully.'),
  120. })
  121. }
  122. })
  123. .finally(() => {
  124. loading.value = false
  125. })
  126. }
  127. const resetFormToDefaults = (
  128. personalSettings: UserData['personalSettings'],
  129. ) => {
  130. form.value?.resetForm({
  131. values: {
  132. matrix: personalSettings?.notificationConfig?.matrix || {},
  133. },
  134. })
  135. }
  136. const onResetToDefaultSettings = async () => {
  137. const confirmed = await waitForConfirmation(
  138. __('Are you sure? Your notifications settings will be reset to default.'),
  139. )
  140. if (!confirmed) return
  141. loading.value = true
  142. const notificationResetMutation = new MutationHandler(
  143. useUserCurrentNotificationPreferencesResetMutation(),
  144. {
  145. errorNotificationMessage: __('Notification settings could not be reset.'),
  146. },
  147. )
  148. return notificationResetMutation
  149. .send()
  150. .then((response) => {
  151. const personalSettings =
  152. response?.userCurrentNotificationPreferencesReset?.user
  153. ?.personalSettings
  154. if (!personalSettings) return
  155. resetFormToDefaults(personalSettings)
  156. notify({
  157. id: 'notification-reset-success',
  158. type: NotificationTypes.Success,
  159. message: __('Notification settings have been reset to default.'),
  160. })
  161. })
  162. .finally(() => {
  163. loading.value = false
  164. })
  165. }
  166. </script>
  167. <template>
  168. <LayoutContent :breadcrumb-items="breadcrumbItems" width="narrow">
  169. <div class="mb-4">
  170. <Form
  171. id="notifications-form"
  172. ref="form"
  173. :schema="schema"
  174. :form-updater-id="EnumFormUpdaterId.FormUpdaterUpdaterUserNotifications"
  175. form-updater-initial-only
  176. :initial-values="initialFormValues"
  177. @submit="onSubmit($event as FormSubmitData<NotificationFormData>)"
  178. >
  179. <template #after-fields>
  180. <div class="flex justify-end gap-2">
  181. <CommonButton
  182. size="medium"
  183. variant="danger"
  184. :disabled="loading"
  185. @click="onResetToDefaultSettings"
  186. >
  187. {{ $t('Reset to Default Settings') }}
  188. </CommonButton>
  189. <CommonButton
  190. size="medium"
  191. type="submit"
  192. variant="submit"
  193. :disabled="loading"
  194. >
  195. {{ $t('Save Notifications') }}
  196. </CommonButton>
  197. </div>
  198. </template>
  199. </Form>
  200. </div>
  201. </LayoutContent>
  202. </template>