CommonUserAvatar.vue 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. <!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { useDateFormat } from '@vueuse/shared'
  4. import { computed } from 'vue'
  5. import { useReactiveNow } from '#shared/composables/useReactiveNow.ts'
  6. import { getIdFromGraphQLId } from '#shared/graphql/utils.ts'
  7. import { i18n } from '#shared/i18n.ts'
  8. import { getUserAvatarClasses } from '#shared/initializer/initializeUserAvatarClasses.ts'
  9. import { useApplicationStore } from '#shared/stores/application.ts'
  10. import {
  11. SYSTEM_USER_ID,
  12. SYSTEM_USER_INTERNAL_ID,
  13. } from '#shared/utils/constants.ts'
  14. import { getInitials } from '#shared/utils/formatter.ts'
  15. import CommonAvatar from '../CommonAvatar/CommonAvatar.vue'
  16. import logo from './assets/logo.svg'
  17. import type { AvatarUser } from './types.ts'
  18. import type { AvatarSize } from '../CommonAvatar/index.ts'
  19. export interface Props {
  20. entity: AvatarUser
  21. size?: AvatarSize
  22. personal?: boolean
  23. decorative?: boolean
  24. initialsOnly?: boolean
  25. }
  26. const props = defineProps<Props>()
  27. const initials = computed(() => {
  28. const { lastname, firstname, email, phone, mobile } = props.entity
  29. return getInitials(firstname, lastname, email, phone, mobile)
  30. })
  31. const { backgroundColors } = getUserAvatarClasses()
  32. const fullName = computed(() => {
  33. const { lastname, firstname, fullname } = props.entity
  34. if (fullname) return fullname
  35. return [firstname, lastname].filter(Boolean).join(' ')
  36. })
  37. const colorClass = computed(() => {
  38. const { id } = props.entity
  39. const internalId = getIdFromGraphQLId(id)
  40. if (internalId === SYSTEM_USER_INTERNAL_ID) return 'bg-white'
  41. // get color based on mod of the integer ID
  42. // so it stays consistent between different interfaces and logins
  43. return backgroundColors[internalId % (backgroundColors.length - 1)]
  44. })
  45. const sources = ['facebook', 'twitter']
  46. const icon = computed(() => {
  47. const { source } = props.entity
  48. if (source && sources.includes(source)) return source
  49. return null
  50. })
  51. const application = useApplicationStore()
  52. const image = computed(() => {
  53. if (icon.value || props.initialsOnly) return null
  54. if (props.entity.id === SYSTEM_USER_ID) return logo
  55. if (!props.entity.image) return null
  56. // Support the inline data URI as an image source.
  57. if (props.entity.image.startsWith('data:')) return props.entity.image
  58. // we're using the REST api here to get the image and to also use the browser image cache
  59. // TODO: this should be re-evaluated when the desktop app is going to be implemented
  60. const apiUrl = String(application.config.api_path)
  61. return `${apiUrl}/users/image/${props.entity.image}`
  62. })
  63. const isVip = computed(() => {
  64. return !props.personal && props.entity.vip
  65. })
  66. const currentDate = useReactiveNow()
  67. const isOutOfOffice = computed(() => {
  68. if (
  69. props.entity.outOfOffice &&
  70. props.entity.outOfOfficeStartAt &&
  71. props.entity.outOfOfficeEndAt
  72. ) {
  73. const today = useDateFormat(currentDate.value, 'YYYY-MM-DD')
  74. const startDate = props.entity?.outOfOfficeStartAt
  75. const endDate = props.entity?.outOfOfficeEndAt
  76. return startDate <= today.value && endDate >= today.value // Today is between start and end date
  77. }
  78. return false
  79. })
  80. const className = computed(() => {
  81. const classes = [colorClass.value]
  82. if (isOutOfOffice.value) {
  83. classes.push('opacity-100 grayscale-[70%]')
  84. } else if (props.entity.active === false) {
  85. classes.push('opacity-20 grayscale')
  86. }
  87. return classes
  88. })
  89. const label = computed(() => {
  90. let label = i18n.t('Avatar')
  91. const name = fullName.value || props.entity.email
  92. if (name) label += ` (${name})`
  93. if (isVip.value) label += ` (${i18n.t('VIP')})`
  94. return label
  95. })
  96. </script>
  97. <template>
  98. <CommonAvatar
  99. :initials="initials"
  100. :size="size"
  101. :icon="icon"
  102. :class="className"
  103. :image="image"
  104. :vip-icon="isVip ? 'vip-user' : undefined"
  105. :decorative="decorative"
  106. :aria-label="label"
  107. />
  108. </template>