Ticket.vue 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  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 page 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 } = useUserTaskbarTabLink(() => {
  40. // Reset the notify flag when the tab becomes active.
  41. if (props.taskbarTab.notify) updateNotifyFlag(false)
  42. })
  43. const isTicketUpdated = computed(() => {
  44. if (tabLinkInstance.value?.isExactActive) return false
  45. return props.taskbarTab.notify
  46. })
  47. const { user } = useSessionStore()
  48. // Set the notify flag whenever the result is received from the subscription.
  49. ticketUpdatesSubscription.onSubscribed().then(() => {
  50. ticketUpdatesSubscription.onResult((result) => {
  51. if (
  52. !result.data?.ticketUpdates ||
  53. !props.taskbarTab.entity ||
  54. props.taskbarTab.notify
  55. )
  56. return
  57. const { ticket } = result.data.ticketUpdates
  58. // Skip setting the notify flag if:
  59. // - the ticket entity is in the same state
  60. // - the ticket was updated by the current user
  61. if (
  62. props.taskbarTab.entity.updatedAt === ticket?.updatedAt ||
  63. ticket?.updatedBy?.id === user?.id
  64. )
  65. return
  66. updateNotifyFlag(true)
  67. })
  68. })
  69. const currentState = computed(() => {
  70. return props.taskbarTab.entity?.state?.name || ''
  71. })
  72. const currentTitle = computed(() => {
  73. return props.taskbarTab.entity?.title || ''
  74. })
  75. const currentStateColorCode = computed(() => {
  76. return (
  77. props.taskbarTab.entity?.stateColorCode || EnumTicketStateColorCode.Open
  78. )
  79. })
  80. const activeBackgroundColor = computed(() => {
  81. switch (currentStateColorCode.value) {
  82. case EnumTicketStateColorCode.Closed:
  83. return '!bg-green-400 text-white dark:text-white'
  84. case EnumTicketStateColorCode.Pending:
  85. return '!bg-stone-400 text-white dark:text-white'
  86. case EnumTicketStateColorCode.Escalating:
  87. return '!bg-red-300 text-white dark:text-white'
  88. case EnumTicketStateColorCode.Open:
  89. default:
  90. return '!bg-yellow-500 text-white dark:text-white'
  91. }
  92. })
  93. const currentViewTitle = computed(
  94. () => `${ticketNumberWithTicketHook.value} - ${currentTitle.value}`,
  95. )
  96. </script>
  97. <template>
  98. <CommonLink
  99. v-if="taskbarTabLink"
  100. ref="tabLinkInstance"
  101. v-tooltip="currentViewTitle"
  102. 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"
  103. :link="taskbarTabLink"
  104. :exact-active-class="activeBackgroundColor"
  105. internal
  106. >
  107. <div class="relative">
  108. <CommonUpdateIndicator v-if="isTicketUpdated" />
  109. <CommonTicketStateIndicatorIcon
  110. class="group-focus-visible/link:text-white"
  111. :color-code="currentStateColorCode"
  112. :label="currentState"
  113. icon-size="small"
  114. />
  115. </div>
  116. <CommonLabel
  117. class="-:text-gray-300 -:dark:text-neutral-400 block truncate group-focus-visible/link:text-white group-hover/tab:dark:text-white"
  118. >
  119. {{ currentTitle }}
  120. </CommonLabel>
  121. </CommonLink>
  122. </template>
  123. <style scoped>
  124. a.router-link-active span {
  125. @apply text-white;
  126. }
  127. </style>