From 15d7bef3be75c2d02ea1725e7bb4682b50ef1af7 Mon Sep 17 00:00:00 2001 From: nsemets Date: Tue, 21 Oct 2025 18:26:31 +0300 Subject: [PATCH] fix(components): added load more button --- .../overview-components.component.html | 11 +++++++++++ .../overview-components.component.ts | 15 +++++++++++++-- .../services/project-overview.service.ts | 16 +++++++++++++--- .../store/project-overview.actions.ts | 11 +++++++++++ .../overview/store/project-overview.model.ts | 8 ++++++-- .../store/project-overview.selectors.ts | 5 +++++ .../overview/store/project-overview.state.ts | 19 ++++++++++++++++--- 7 files changed, 75 insertions(+), 10 deletions(-) diff --git a/src/app/features/project/overview/components/overview-components/overview-components.component.html b/src/app/features/project/overview/components/overview-components/overview-components.component.html index 14fde2e88..19d802ee1 100644 --- a/src/app/features/project/overview/components/overview-components/overview-components.component.html +++ b/src/app/features/project/overview/components/overview-components/overview-components.component.html @@ -70,6 +70,17 @@

} } + + @if (hasMoreComponents()) { +
+ +
+ } } @else {
} diff --git a/src/app/features/project/overview/components/overview-components/overview-components.component.ts b/src/app/features/project/overview/components/overview-components/overview-components.component.ts index 1bd7164c0..e66832c11 100644 --- a/src/app/features/project/overview/components/overview-components/overview-components.component.ts +++ b/src/app/features/project/overview/components/overview-components/overview-components.component.ts @@ -15,7 +15,7 @@ import { CustomDialogService, LoaderService } from '@osf/shared/services'; import { GetResourceWithChildren } from '@osf/shared/stores'; import { ComponentOverview } from '@shared/models'; -import { ProjectOverviewSelectors } from '../../store'; +import { LoadMoreComponents, ProjectOverviewSelectors } from '../../store'; import { AddComponentDialogComponent } from '../add-component-dialog/add-component-dialog.component'; import { DeleteComponentDialogComponent } from '../delete-component-dialog/delete-component-dialog.component'; @@ -36,9 +36,13 @@ export class OverviewComponentsComponent { components = select(ProjectOverviewSelectors.getComponents); isComponentsLoading = select(ProjectOverviewSelectors.getComponentsLoading); + hasMoreComponents = select(ProjectOverviewSelectors.hasMoreComponents); project = select(ProjectOverviewSelectors.getProject); - actions = createDispatchMap({ getComponentsTree: GetResourceWithChildren }); + actions = createDispatchMap({ + getComponentsTree: GetResourceWithChildren, + loadMoreComponents: LoadMoreComponents, + }); readonly UserPermissions = UserPermissions; @@ -96,6 +100,13 @@ export class OverviewComponentsComponent { window.open(url, '_self'); } + loadMoreComponents(): void { + const project = this.project(); + if (!project) return; + + this.actions.loadMoreComponents(project.id); + } + private handleDeleteComponent(componentId: string): void { const project = this.project(); if (!project) return; diff --git a/src/app/features/project/overview/services/project-overview.service.ts b/src/app/features/project/overview/services/project-overview.service.ts index c362782f1..4261b22a8 100644 --- a/src/app/features/project/overview/services/project-overview.service.ts +++ b/src/app/features/project/overview/services/project-overview.service.ts @@ -13,6 +13,8 @@ import { ComponentGetResponseJsonApi, ComponentOverview, JsonApiResponse, + PaginatedData, + ResponseJsonApi, } from '@osf/shared/models'; import { JsonApiService } from '@osf/shared/services'; @@ -153,15 +155,23 @@ export class ProjectOverviewService { return this.jsonApiService.delete(`${this.apiUrl}/nodes/${componentId}/`); } - getComponents(projectId: string): Observable { + getComponents(projectId: string, page = 1, pageSize = 10): Observable> { const params: Record = { embed: 'bibliographic_contributors', 'fields[users]': 'family_name,full_name,given_name,middle_name', + page: page, + 'page[size]': pageSize, }; return this.jsonApiService - .get>(`${this.apiUrl}/nodes/${projectId}/children/`, params) - .pipe(map((response) => response.data.map((item) => ComponentsMapper.fromGetComponentResponse(item)))); + .get>(`${this.apiUrl}/nodes/${projectId}/children/`, params) + .pipe( + map((response) => ({ + data: response.data.map((item) => ComponentsMapper.fromGetComponentResponse(item)), + totalCount: response.meta?.total || 0, + pageSize: response.meta?.per_page || pageSize, + })) + ); } getParentProject(projectId: string): Observable { diff --git a/src/app/features/project/overview/store/project-overview.actions.ts b/src/app/features/project/overview/store/project-overview.actions.ts index f7da561f1..7c812c118 100644 --- a/src/app/features/project/overview/store/project-overview.actions.ts +++ b/src/app/features/project/overview/store/project-overview.actions.ts @@ -1,3 +1,4 @@ +import { DEFAULT_TABLE_PARAMS } from '@osf/shared/constants'; import { ResourceType } from '@shared/enums'; import { PrivacyStatusModel } from '../models'; @@ -69,6 +70,16 @@ export class DeleteComponent { export class GetComponents { static readonly type = '[Project Overview] Get Components'; + constructor( + public projectId: string, + public page = 1, + public pageSize = DEFAULT_TABLE_PARAMS.rows + ) {} +} + +export class LoadMoreComponents { + static readonly type = '[Project Overview] Load More Components'; + constructor(public projectId: string) {} } diff --git a/src/app/features/project/overview/store/project-overview.model.ts b/src/app/features/project/overview/store/project-overview.model.ts index ee522bc05..7bafca3fc 100644 --- a/src/app/features/project/overview/store/project-overview.model.ts +++ b/src/app/features/project/overview/store/project-overview.model.ts @@ -1,10 +1,12 @@ -import { AsyncStateModel, BaseNodeModel, ComponentOverview } from '@osf/shared/models'; +import { AsyncStateModel, AsyncStateWithTotalCount, BaseNodeModel, ComponentOverview } from '@osf/shared/models'; import { ProjectOverview } from '../models'; export interface ProjectOverviewStateModel { project: AsyncStateModel; - components: AsyncStateModel; + components: AsyncStateWithTotalCount & { + currentPage: number; + }; isAnonymous: boolean; duplicatedProject: BaseNodeModel | null; parentProject: AsyncStateModel; @@ -22,6 +24,8 @@ export const PROJECT_OVERVIEW_DEFAULTS: ProjectOverviewStateModel = { isLoading: false, isSubmitting: false, error: null, + currentPage: 0, + totalCount: 0, }, isAnonymous: false, duplicatedProject: null, diff --git a/src/app/features/project/overview/store/project-overview.selectors.ts b/src/app/features/project/overview/store/project-overview.selectors.ts index 6cc9489a5..a0948c18f 100644 --- a/src/app/features/project/overview/store/project-overview.selectors.ts +++ b/src/app/features/project/overview/store/project-overview.selectors.ts @@ -85,4 +85,9 @@ export class ProjectOverviewSelectors { static getParentProjectLoading(state: ProjectOverviewStateModel) { return state.parentProject.isLoading; } + + @Selector([ProjectOverviewState]) + static hasMoreComponents(state: ProjectOverviewStateModel) { + return state.components.data.length < state.components.totalCount && !state.components.isLoading; + } } diff --git a/src/app/features/project/overview/store/project-overview.state.ts b/src/app/features/project/overview/store/project-overview.state.ts index 07bd9b29c..0ba05b25c 100644 --- a/src/app/features/project/overview/store/project-overview.state.ts +++ b/src/app/features/project/overview/store/project-overview.state.ts @@ -19,6 +19,7 @@ import { GetComponents, GetParentProject, GetProjectById, + LoadMoreComponents, SetProjectCustomCitation, UpdateProjectPublicStatus, } from './project-overview.actions'; @@ -253,13 +254,17 @@ export class ProjectOverviewState { }, }); - return this.projectOverviewService.getComponents(action.projectId).pipe( - tap((components) => { + return this.projectOverviewService.getComponents(action.projectId, action.page, action.pageSize).pipe( + tap((response) => { + const data = action.page === 1 ? response.data : [...state.components.data, ...response.data]; + ctx.patchState({ components: { - data: components, + data, isLoading: false, error: null, + currentPage: action.page, + totalCount: response.totalCount, }, }); }), @@ -267,6 +272,14 @@ export class ProjectOverviewState { ); } + @Action(LoadMoreComponents) + loadMoreComponents(ctx: StateContext, action: LoadMoreComponents) { + const state = ctx.getState(); + const nextPage = state.components.currentPage + 1; + + return ctx.dispatch(new GetComponents(action.projectId, nextPage, 10)); + } + @Action(GetParentProject) getParentProject(ctx: StateContext, action: GetParentProject) { const state = ctx.getState();