UserDetailView.vue 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. <!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { computed, ref, toRef } from 'vue'
  4. import CommonUserAvatar from '#shared/components/CommonUserAvatar/CommonUserAvatar.vue'
  5. import ObjectAttributes from '#shared/components/ObjectAttributes/ObjectAttributes.vue'
  6. import { useOnlineNotificationSeen } from '#shared/composables/useOnlineNotificationSeen.ts'
  7. import { useUserDetail } from '#shared/entities/user/composables/useUserDetail.ts'
  8. import { useErrorHandler } from '#shared/errors/useErrorHandler.ts'
  9. import CommonButtonGroup from '#mobile/components/CommonButtonGroup/CommonButtonGroup.vue'
  10. import type { CommonButtonOption } from '#mobile/components/CommonButtonGroup/types.ts'
  11. import CommonLoader from '#mobile/components/CommonLoader/CommonLoader.vue'
  12. import CommonOrganizationsList from '#mobile/components/CommonOrganizationsList/CommonOrganizationsList.vue'
  13. import CommonTicketStateList from '#mobile/components/CommonTicketStateList/CommonTicketStateList.vue'
  14. import { useHeader } from '#mobile/composables/useHeader.ts'
  15. import { useOrganizationTicketsCount } from '#mobile/entities/organization/composables/useOrganizationTicketsCount.ts'
  16. import { useUserEdit } from '#mobile/entities/user/composables/useUserEdit.ts'
  17. import { useUsersTicketsCount } from '#mobile/entities/user/composables/useUserTicketsCount.ts'
  18. interface Props {
  19. internalId: number
  20. }
  21. const props = defineProps<Props>()
  22. const { createQueryErrorHandler } = useErrorHandler()
  23. const errorCallback = createQueryErrorHandler({
  24. notFound: __(
  25. 'User with specified ID was not found. Try checking the URL for errors.',
  26. ),
  27. forbidden: __('You have insufficient rights to view this user.'),
  28. })
  29. const {
  30. user,
  31. userQuery,
  32. loading,
  33. objectAttributes,
  34. secondaryOrganizations,
  35. loadAllSecondaryOrganizations,
  36. } = useUserDetail(toRef(props, 'internalId'), errorCallback)
  37. useOnlineNotificationSeen(user)
  38. const { openEditUserDialog } = useUserEdit()
  39. useHeader({
  40. title: __('User'),
  41. backUrl: '/',
  42. actionTitle: __('Edit'),
  43. actionHidden: computed(() => user.value == null || !user.value.policy.update),
  44. refetch: computed(() => user.value != null && userQuery.loading().value),
  45. onAction() {
  46. if (!user.value || !user.value.policy.update) return
  47. openEditUserDialog(user.value)
  48. },
  49. })
  50. enum TicketLinksState {
  51. User = 'user',
  52. Organization = 'organization',
  53. }
  54. const ticketView = ref(TicketLinksState.User)
  55. const ticketButtons = computed<CommonButtonOption[]>(() => {
  56. if (!user.value?.organization) return []
  57. return [
  58. {
  59. label: __('Their tickets'),
  60. value: TicketLinksState.User,
  61. controls: `tab-${TicketLinksState.User}`,
  62. },
  63. {
  64. label: __('Organization tickets'),
  65. value: TicketLinksState.Organization,
  66. controls: `tab-${TicketLinksState.Organization}`,
  67. },
  68. ]
  69. })
  70. const { getTicketData: getOrganizationTicketsData } =
  71. useOrganizationTicketsCount()
  72. const { getTicketData: getUserTicketsData } = useUsersTicketsCount()
  73. const ticketsData = computed(() => {
  74. if (!user.value) return null
  75. if (ticketView.value === TicketLinksState.User) {
  76. return getUserTicketsData(user.value)
  77. }
  78. const { organization } = user.value
  79. if (!organization) return null
  80. return getOrganizationTicketsData(organization)
  81. })
  82. </script>
  83. <template>
  84. <div v-if="user" class="px-4">
  85. <div class="flex flex-col items-center justify-center py-6">
  86. <div>
  87. <CommonUserAvatar :entity="user" size="xl" />
  88. </div>
  89. <div class="mt-2 line-clamp-3 text-center text-xl font-bold">
  90. {{ user.fullname }}
  91. </div>
  92. <CommonLink
  93. v-if="user.organization"
  94. data-test-id="organization-link"
  95. :link="`/organizations/${user.organization.internalId}`"
  96. class="text-blue text-center text-base"
  97. >
  98. {{ user.organization.name }}
  99. </CommonLink>
  100. </div>
  101. <ObjectAttributes
  102. :attributes="objectAttributes"
  103. :object="user"
  104. :skip-attributes="['firstname', 'lastname']"
  105. />
  106. <CommonOrganizationsList
  107. :organizations="secondaryOrganizations.array"
  108. :total-count="secondaryOrganizations.totalCount"
  109. :disable-show-more="loading"
  110. :label="__('Secondary organizations')"
  111. @show-more="loadAllSecondaryOrganizations()"
  112. />
  113. <CommonTicketStateList
  114. v-if="ticketsData"
  115. :id="`tab-${ticketView}`"
  116. :create-link="ticketsData.createLink"
  117. :create-label="ticketsData.createLabel"
  118. :counts="ticketsData.count"
  119. :tickets-link-query="ticketsData.query"
  120. role="tabpanel"
  121. aria-live="polite"
  122. >
  123. <template #before-fields>
  124. <CommonButtonGroup
  125. v-if="ticketButtons.length"
  126. v-model="ticketView"
  127. as="tabs"
  128. class="py-2"
  129. :options="ticketButtons"
  130. />
  131. </template>
  132. </CommonTicketStateList>
  133. </div>
  134. <CommonLoader v-else :loading="loading" />
  135. </template>