Ticket.vue 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. <!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { computed, toRef } from 'vue'
  4. import { useTicketUpdatesSubscription } from '#shared/entities/ticket/graphql/subscriptions/ticketUpdates.api.ts'
  5. import { EnumTicketStateColorCode, type Ticket } from '#shared/graphql/types.ts'
  6. import SubscriptionHandler from '#shared/server/apollo/handler/SubscriptionHandler.ts'
  7. import { useSessionStore } from '#shared/stores/session.ts'
  8. import { GraphQLErrorTypes } from '#shared/types/error.ts'
  9. import CommonTicketStateIndicatorIcon from '#desktop/components/CommonTicketStateIndicatorIcon/CommonTicketStateIndicatorIcon.vue'
  10. import CommonUpdateIndicator from '#desktop/components/CommonUpdateIndicator/CommonUpdateIndicator.vue'
  11. import { useUserTaskbarTabLink } from '#desktop/composables/useUserTaskbarTabLink.ts'
  12. import { useUserCurrentTaskbarTabsStore } from '#desktop/entities/user/current/stores/taskbarTabs.ts'
  13. import { useTicketNumber } from '#desktop/pages/ticket/composables/useTicketNumber.ts'
  14. import type { UserTaskbarTabEntityProps } from '../types.ts'
  15. const props = defineProps<UserTaskbarTabEntityProps<Ticket>>()
  16. const { ticketNumberWithTicketHook } = useTicketNumber(
  17. toRef(props.taskbarTab, 'entity'),
  18. )
  19. const ticketUpdatesSubscription = new SubscriptionHandler(
  20. useTicketUpdatesSubscription({
  21. ticketId: props.taskbarTab.entity!.id,
  22. initial: true,
  23. }),
  24. {
  25. // NB: Silence toast notifications for particular errors, these will be handled by the layout taskbar tab component.
  26. errorCallback: (errorHandler) =>
  27. errorHandler.type !== GraphQLErrorTypes.Forbidden &&
  28. errorHandler.type !== GraphQLErrorTypes.RecordNotFound,
  29. },
  30. )
  31. const { updateTaskbarTab } = useUserCurrentTaskbarTabsStore()
  32. const updateNotifyFlag = (notify: boolean) => {
  33. if (!props.taskbarTab.taskbarTabId) return
  34. updateTaskbarTab(props.taskbarTab.taskbarTabId, {
  35. ...props.taskbarTab,
  36. notify,
  37. })
  38. }
  39. const { tabLinkInstance, taskbarTabActive } = useUserTaskbarTabLink(
  40. toRef(props, 'taskbarTab'),
  41. () => {
  42. // Reset the notify flag when the tab becomes active.
  43. if (props.taskbarTab.notify) updateNotifyFlag(false)
  44. },
  45. )
  46. const isTicketUpdated = computed(() => {
  47. if (taskbarTabActive.value) return false
  48. return props.taskbarTab.notify
  49. })
  50. const { user } = useSessionStore()
  51. // Set the notify flag whenever the result is received from the subscription.
  52. ticketUpdatesSubscription.onSubscribed().then(() => {
  53. ticketUpdatesSubscription.onResult((result) => {
  54. if (
  55. !result.data?.ticketUpdates ||
  56. !props.taskbarTab.entity ||
  57. props.taskbarTab.notify
  58. )
  59. return
  60. const { ticket } = result.data.ticketUpdates
  61. // Skip setting the notify flag if:
  62. // - the ticket entity is in the same state
  63. // - the ticket was updated by the current user
  64. if (
  65. props.taskbarTab.entity.updatedAt === ticket?.updatedAt ||
  66. ticket?.updatedBy?.id === user?.id
  67. )
  68. return
  69. updateNotifyFlag(true)
  70. })
  71. })
  72. const currentState = computed(() => {
  73. return props.taskbarTab.entity?.state?.name || ''
  74. })
  75. const currentTitle = computed(() => {
  76. return props.taskbarTab.entity?.title || ''
  77. })
  78. const currentStateColorCode = computed(
  79. () =>
  80. props.taskbarTab.entity?.stateColorCode || EnumTicketStateColorCode.Open,
  81. )
  82. const activeBackgroundColor = computed(() => {
  83. switch (currentStateColorCode.value) {
  84. case EnumTicketStateColorCode.Closed:
  85. return '!bg-green-400 text-white dark:text-white'
  86. case EnumTicketStateColorCode.Pending:
  87. return '!bg-stone-400 text-white dark:text-white'
  88. case EnumTicketStateColorCode.Escalating:
  89. return '!bg-red-300 text-white dark:text-white'
  90. case EnumTicketStateColorCode.Open:
  91. default:
  92. return '!bg-yellow-500 text-white dark:text-white'
  93. }
  94. })
  95. const currentViewTitle = computed(
  96. () => `${ticketNumberWithTicketHook.value} - ${currentTitle.value}`,
  97. )
  98. </script>
  99. <template>
  100. <CommonLink
  101. v-if="taskbarTabLink"
  102. ref="tabLinkInstance"
  103. v-tooltip="currentViewTitle"
  104. class="flex grow gap-2 rounded-md px-2 py-3 hover:no-underline focus-visible:rounded-md focus-visible:outline-none group-hover/tab:bg-blue-600 group-hover/tab:dark:bg-blue-900"
  105. :link="taskbarTabLink"
  106. :class="{
  107. [activeBackgroundColor]: taskbarTabActive,
  108. }"
  109. internal
  110. >
  111. <div class="relative">
  112. <CommonUpdateIndicator v-if="isTicketUpdated" />
  113. <CommonTicketStateIndicatorIcon
  114. class="group-focus-visible/link:text-white"
  115. :class="{
  116. '!text-white': taskbarTabActive,
  117. }"
  118. :color-code="currentStateColorCode"
  119. :label="currentState"
  120. icon-size="small"
  121. />
  122. </div>
  123. <CommonLabel
  124. class="-:text-gray-300 -:dark:text-neutral-400 block truncate group-focus-visible/link:text-white group-hover/tab:dark:text-white"
  125. :class="{
  126. '!text-white': taskbarTabActive,
  127. }"
  128. >
  129. {{ currentTitle }}
  130. </CommonLabel>
  131. </CommonLink>
  132. </template>