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();