Просмотр исходного кода

Feature: Mobile - Fetch secondary organizations for user view

Vladimir Sheremet 2 лет назад
Родитель
Сommit
a4ea246edd

+ 21 - 1
app/frontend/apps/mobile/components/CommonOrganizationsList/CommonOrganizationsList.vue

@@ -7,10 +7,18 @@ import CommonSectionMenu from '../CommonSectionMenu/CommonSectionMenu.vue'
 
 interface Props {
   organizations: (AvatarOrganization & { id: string; internalId: number })[]
+  totalCount: number
+  disableShowMore?: boolean
   label?: string
 }
 
-defineProps<Props>()
+withDefaults(defineProps<Props>(), {
+  disableShowMore: false,
+})
+
+const emit = defineEmits<{
+  (e: 'showMore'): void
+}>()
 </script>
 
 <template>
@@ -29,5 +37,17 @@ defineProps<Props>()
         {{ organization.name }}
       </span>
     </CommonLink>
+    <button
+      v-if="organizations.length < totalCount"
+      class="flex min-h-[54px] items-center justify-center gap-2"
+      :class="{
+        'cursor-default text-gray-100/50': disableShowMore,
+        'text-blue': !disableShowMore,
+      }"
+      :disabled="disableShowMore"
+      @click="emit('showMore')"
+    >
+      {{ $t('Show %s more', totalCount - organizations.length) }}
+    </button>
   </CommonSectionMenu>
 </template>

+ 83 - 0
app/frontend/apps/mobile/entities/organization/composables/useOrganizationDetail.ts

@@ -0,0 +1,83 @@
+// Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
+
+import { useObjectAttributes } from '@shared/entities/object-attributes/composables/useObjectAttributes'
+import type {
+  OrganizationUpdatesSubscriptionVariables,
+  OrganizationUpdatesSubscription,
+} from '@shared/graphql/types'
+import { EnumObjectManagerObjects } from '@shared/graphql/types'
+import { QueryHandler } from '@shared/server/apollo/handler'
+import { computed, nextTick, ref, watch } from 'vue'
+import { useOrganizationLazyQuery } from '../graphql/queries/organization.api'
+import { OrganizationUpdatesDocument } from '../graphql/subscriptions/organizationUpdates.api'
+
+export const useOrganizationDetail = () => {
+  const internalId = ref(0)
+
+  const organizationQuery = new QueryHandler(
+    useOrganizationLazyQuery(
+      () => ({
+        organizationInternalId: internalId.value,
+        membersCount: 3,
+      }),
+      () => ({ enabled: internalId.value > 0 }),
+    ),
+  )
+
+  const loadOrganization = (id: number) => {
+    internalId.value = id
+    nextTick(() => {
+      organizationQuery.load()
+    })
+  }
+
+  const organizationResult = organizationQuery.result()
+  const loading = organizationQuery.loading()
+
+  const organization = computed(() => organizationResult.value?.organization)
+
+  const loadAllMembers = () => {
+    const organizationInternalId = organization.value?.internalId
+    if (!organizationInternalId) {
+      return
+    }
+
+    organizationQuery.refetch({
+      organizationInternalId,
+      membersCount: null,
+    })
+  }
+
+  const { attributes: objectAttributes } = useObjectAttributes(
+    EnumObjectManagerObjects.Organization,
+  )
+
+  watch(
+    () => organization.value?.id,
+    (organizationId) => {
+      if (!organizationId) {
+        return
+      }
+
+      organizationQuery.subscribeToMore<
+        OrganizationUpdatesSubscriptionVariables,
+        OrganizationUpdatesSubscription
+      >({
+        document: OrganizationUpdatesDocument,
+        variables: {
+          organizationId,
+        },
+      })
+    },
+    { immediate: true },
+  )
+
+  return {
+    loading,
+    organizationQuery,
+    organization,
+    objectAttributes,
+    loadOrganization,
+    loadAllMembers,
+  }
+}

+ 16 - 0
app/frontend/apps/mobile/entities/user/__tests__/mocks/user-mocks.ts

@@ -34,6 +34,22 @@ export const defaultUser = (): ConfidentTake<UserQuery, 'user'> => {
       name: organization.name,
       ticketsCount: organization.ticketsCount,
     },
+    secondaryOrganizations: {
+      __typename: 'OrganizationConnection',
+      edges: [
+        {
+          __typename: 'OrganizationEdge',
+          node: {
+            __typename: 'Organization',
+            id: 'dsa34fsa223d',
+            name: 'Dammaz',
+            internalId: 10,
+            active: true,
+          },
+        },
+      ],
+      totalCount: 1,
+    },
     objectAttributeValues: [
       {
         attribute: {

+ 16 - 9
app/frontend/apps/mobile/entities/user/composables/useUserDetail.ts

@@ -6,8 +6,7 @@ import type {
   UserUpdatesSubscription,
 } from '@shared/graphql/types'
 import { QueryHandler } from '@shared/server/apollo/handler'
-import { whenever } from '@vueuse/shared'
-import { computed, nextTick, ref } from 'vue'
+import { computed, nextTick, ref, watch } from 'vue'
 import { useUserObjectAttributesStore } from '@shared/entities/user/stores/objectAttributes'
 import { useUserLazyQuery } from '../graphql/queries/user.api'
 
@@ -18,6 +17,7 @@ export const useUserDetail = () => {
     useUserLazyQuery(
       () => ({
         userInternalId: internalId.value,
+        secondaryOrganizationsCount: 3,
       }),
       () => ({ enabled: internalId.value > 0 }),
     ),
@@ -30,6 +30,13 @@ export const useUserDetail = () => {
     })
   }
 
+  const loadAllSecondaryOrganizations = () => {
+    userQuery.refetch({
+      userInternalId: internalId.value,
+      secondaryOrganizationsCount: null,
+    })
+  }
+
   const userResult = userQuery.result()
   const loading = userQuery.loading()
 
@@ -41,12 +48,10 @@ export const useUserDetail = () => {
     () => objectAttributesManager.viewScreenAttributes || [],
   )
 
-  const stopWatch = whenever(
-    () => user.value != null,
-    () => {
-      if (!user.value) return
-
-      stopWatch()
+  watch(
+    () => user.value?.id,
+    (userId) => {
+      if (!userId) return
 
       userQuery.subscribeToMore<
         UserUpdatesSubscriptionVariables,
@@ -54,16 +59,18 @@ export const useUserDetail = () => {
       >({
         document: UserUpdatesDocument,
         variables: {
-          userId: user.value.id,
+          userId,
         },
       })
     },
+    { immediate: true },
   )
 
   return {
     loading,
     user,
     objectAttributes,
+    loadAllSecondaryOrganizations,
     loadUser,
   }
 }

+ 12 - 1
app/frontend/apps/mobile/entities/user/graphql/queries/user.api.ts

@@ -7,9 +7,20 @@ import * as VueCompositionApi from 'vue';
 export type ReactiveFunction<TParam> = () => TParam;
 
 export const UserDocument = gql`
-    query user($userId: ID, $userInternalId: Int) {
+    query user($userId: ID, $userInternalId: Int, $secondaryOrganizationsCount: Int) {
   user(user: {userId: $userId, userInternalId: $userInternalId}) {
     ...userDetailAttributes
+    secondaryOrganizations(first: $secondaryOrganizationsCount) {
+      edges {
+        node {
+          id
+          internalId
+          active
+          name
+        }
+      }
+      totalCount
+    }
   }
 }
     ${UserDetailAttributesFragmentDoc}`;

+ 16 - 1
app/frontend/apps/mobile/entities/user/graphql/queries/user.graphql

@@ -1,5 +1,20 @@
-query user($userId: ID, $userInternalId: Int) {
+query user(
+  $userId: ID
+  $userInternalId: Int
+  $secondaryOrganizationsCount: Int
+) {
   user(user: { userId: $userId, userInternalId: $userInternalId }) {
     ...userDetailAttributes
+    secondaryOrganizations(first: $secondaryOrganizationsCount) {
+      edges {
+        node {
+          id
+          internalId
+          active
+          name
+        }
+      }
+      totalCount
+    }
   }
 }

+ 2 - 2
app/frontend/apps/mobile/pages/organization/__tests__/view-organization.spec.ts

@@ -27,7 +27,7 @@ describe('static organization', () => {
     )
     mockOrganizationObjectAttributes()
 
-    const view = await visitView('/organizations/3423225dsf0')
+    const view = await visitView(`/organizations/${organization.internalId}`)
 
     await waitUntil(() => mockApi.calls.resolve)
 
@@ -85,7 +85,7 @@ describe('static organization', () => {
     mockGraphQLSubscription(OrganizationUpdatesDocument)
     mockOrganizationObjectAttributes()
 
-    const view = await visitView('/organizations/3423225dsf0')
+    const view = await visitView(`/organizations/${organization.internalId}`)
 
     await waitUntil(() => mockApi.calls.resolve)
 

+ 2 - 2
app/frontend/apps/mobile/pages/organization/routes.ts

@@ -4,9 +4,9 @@ import type { RouteRecordRaw } from 'vue-router'
 
 const routes: RouteRecordRaw[] = [
   {
-    path: '/organizations/:id',
+    path: '/organizations/:internalId',
     name: 'OrganizationDetailView',
-    props: true,
+    props: (route) => ({ internalId: Number(route.params.internalId) }),
     component: () => import('./views/OrganizationDetailView.vue'),
     meta: {
       title: __('Organization'),

+ 11 - 35
app/frontend/apps/mobile/pages/organization/views/OrganizationDetailView.vue

@@ -5,40 +5,34 @@
 import { useRouter } from 'vue-router'
 import { computed } from 'vue'
 import CommonOrganizationAvatar from '@shared/components/CommonOrganizationAvatar/CommonOrganizationAvatar.vue'
-import { QueryHandler } from '@shared/server/apollo/handler'
 import { useHeader } from '@mobile/composables/useHeader'
 import CommonLoader from '@mobile/components/CommonLoader/CommonLoader.vue'
 import CommonTicketStateList from '@mobile/components/CommonTicketStateList/CommonTicketStateList.vue'
 import { redirectToError } from '@mobile/router/error'
 import { ErrorStatusCodes } from '@shared/types/error'
-import { useOrganizationQuery } from '@mobile/entities/organization/graphql/queries/organization.api'
-import { OrganizationUpdatesDocument } from '@mobile/entities/organization/graphql/subscriptions/organizationUpdates.api'
 import { useOrganizationEdit } from '@mobile/entities/organization/composables/useOrganizationEdit'
 import OrganizationMembersList from '@mobile/components/Organization/OrganizationMembersList.vue'
-import { useOrganizationObjectAttributesStore } from '@shared/entities/organization/stores/objectAttributes'
 import { AvatarOrganization } from '@shared/components/CommonOrganizationAvatar'
 import CommonObjectAttributes from '@mobile/components/CommonObjectAttributes/CommonObjectAttributes.vue'
 import { useOrganizationTicketsCount } from '@mobile/entities/organization/composables/useOrganizationTicketsCount'
+import { useOrganizationDetail } from '@mobile/entities/organization/composables/useOrganizationDetail'
 
 interface Props {
-  id: string
+  internalId: number
 }
 
 const props = defineProps<Props>()
 
-const organizationQuery = new QueryHandler(
-  useOrganizationQuery({
-    organizationId: props.id,
-    membersCount: 3,
-  }),
-)
+const {
+  organization,
+  organizationQuery,
+  loading,
+  objectAttributes,
+  loadAllMembers,
+  loadOrganization,
+} = useOrganizationDetail()
 
-organizationQuery.subscribeToMore({
-  document: OrganizationUpdatesDocument,
-  variables: {
-    organizationId: props.id,
-  },
-})
+loadOrganization(props.internalId)
 
 const router = useRouter()
 
@@ -49,17 +43,6 @@ organizationQuery.onError(() => {
   })
 })
 
-const organizationResult = organizationQuery.result()
-const loading = organizationQuery.loading()
-
-const organization = computed(() => organizationResult.value?.organization)
-
-const objectAttributesManager = useOrganizationObjectAttributesStore()
-
-const objectAttributes = computed(
-  () => objectAttributesManager.viewScreenAttributes || [],
-)
-
 const { openEditOrganizationDialog } = useOrganizationEdit()
 
 useHeader({
@@ -72,13 +55,6 @@ useHeader({
   },
 })
 
-const loadAllMembers = () => {
-  organizationQuery.refetch({
-    organizationId: props.id,
-    membersCount: null,
-  })
-}
-
 const { getTicketData } = useOrganizationTicketsCount()
 const ticketData = computed(() => getTicketData(organization.value))
 </script>

+ 1 - 0
app/frontend/apps/mobile/pages/ticket/__tests__/mocks/detail-view.ts

@@ -43,6 +43,7 @@ export const defaultTicket = () =>
       organization: {
         __typename: 'Organization',
         id: '3423225dsf0',
+        internalId: 300,
         name: 'Zammad Foundation',
       },
       state: {

Некоторые файлы не были показаны из-за большого количества измененных файлов