diff --git a/src/app/features/admin-institutions/constants/index.ts b/src/app/features/admin-institutions/constants/index.ts
index 9044772a9..7c1360d02 100644
--- a/src/app/features/admin-institutions/constants/index.ts
+++ b/src/app/features/admin-institutions/constants/index.ts
@@ -1,4 +1,6 @@
export * from './admin-table-columns.constant';
export * from './department-options.constant';
+export * from './preprints-table-columns.constant';
export * from './project-table-columns.constant';
+export * from './registration-table-columns.constant';
export * from './resource-tab-option.constant';
diff --git a/src/app/features/admin-institutions/constants/preprints-table-columns.constant.ts b/src/app/features/admin-institutions/constants/preprints-table-columns.constant.ts
new file mode 100644
index 000000000..a17b765d7
--- /dev/null
+++ b/src/app/features/admin-institutions/constants/preprints-table-columns.constant.ts
@@ -0,0 +1,58 @@
+import { TableColumn } from '@osf/features/admin-institutions/models';
+
+export const preprintsTableColumns: TableColumn[] = [
+ {
+ field: 'title',
+ header: 'adminInstitutions.projects.title',
+ isLink: true,
+ linkTarget: '_blank',
+ },
+ {
+ field: 'link',
+ header: 'adminInstitutions.projects.link',
+ sortable: false,
+ isLink: true,
+ linkTarget: '_blank',
+ },
+ {
+ field: 'dateCreated',
+ header: 'adminInstitutions.projects.dateCreated',
+ sortable: true,
+ dateFormat: 'dd/MM/yyyy',
+ },
+ {
+ field: 'dateModified',
+ header: 'adminInstitutions.projects.dateModified',
+ sortable: true,
+ dateFormat: 'dd/MM/yyyy',
+ },
+ {
+ field: 'doi',
+ header: 'adminInstitutions.projects.doi',
+ isLink: true,
+ linkTarget: '_blank',
+ sortable: false,
+ },
+ {
+ field: 'license',
+ header: 'adminInstitutions.projects.license',
+ sortable: false,
+ },
+ {
+ field: 'contributorName',
+ header: 'adminInstitutions.projects.contributorName',
+ sortable: true,
+ isLink: true,
+ linkTarget: '_blank',
+ },
+ {
+ field: 'viewsLast30Days',
+ header: 'adminInstitutions.projects.views',
+ sortable: false,
+ },
+ {
+ field: 'downloadsLast30Days',
+ header: 'adminInstitutions.preprints.downloadsLastDays',
+ sortable: false,
+ },
+];
diff --git a/src/app/features/admin-institutions/constants/registration-table-columns.constant.ts b/src/app/features/admin-institutions/constants/registration-table-columns.constant.ts
new file mode 100644
index 000000000..b301d7174
--- /dev/null
+++ b/src/app/features/admin-institutions/constants/registration-table-columns.constant.ts
@@ -0,0 +1,79 @@
+import { TableColumn } from '@osf/features/admin-institutions/models';
+
+export const registrationTableColumns: TableColumn[] = [
+ {
+ field: 'title',
+ header: 'adminInstitutions.projects.title',
+ sortable: false,
+ isLink: true,
+ linkTarget: '_blank',
+ },
+ {
+ field: 'link',
+ header: 'adminInstitutions.projects.link',
+ sortable: false,
+ isLink: true,
+ linkTarget: '_blank',
+ },
+ {
+ field: 'dateCreated',
+ header: 'adminInstitutions.projects.dateCreated',
+ sortable: true,
+ dateFormat: 'dd/MM/yyyy',
+ },
+ {
+ field: 'dateModified',
+ header: 'adminInstitutions.projects.dateModified',
+ sortable: true,
+ dateFormat: 'dd/MM/yyyy',
+ },
+ {
+ field: 'doi',
+ header: 'adminInstitutions.projects.doi',
+ sortable: false,
+ isLink: true,
+ linkTarget: '_blank',
+ },
+ {
+ field: 'storageLocation',
+ header: 'adminInstitutions.projects.storageLocation',
+ sortable: false,
+ },
+ {
+ field: 'totalDataStored',
+ header: 'adminInstitutions.projects.totalDataStored',
+ sortable: false,
+ },
+ {
+ field: 'contributorName',
+ header: 'adminInstitutions.projects.contributorName',
+ sortable: true,
+ isLink: true,
+ linkTarget: '_blank',
+ },
+ {
+ field: 'views',
+ header: 'adminInstitutions.projects.views',
+ sortable: false,
+ },
+ {
+ field: 'resourceType',
+ header: 'adminInstitutions.projects.resourceType',
+ sortable: false,
+ },
+ {
+ field: 'license',
+ header: 'adminInstitutions.projects.license',
+ sortable: false,
+ },
+ {
+ field: 'funderName',
+ header: 'adminInstitutions.registrations.funderName',
+ sortable: false,
+ },
+ {
+ field: 'registrationSchema',
+ header: 'adminInstitutions.registrations.registrationSchema',
+ sortable: false,
+ },
+];
diff --git a/src/app/features/admin-institutions/helpers/extract-path-after-domain.ts b/src/app/features/admin-institutions/helpers/extract-path-after-domain.ts
new file mode 100644
index 000000000..019c44bb6
--- /dev/null
+++ b/src/app/features/admin-institutions/helpers/extract-path-after-domain.ts
@@ -0,0 +1,4 @@
+export function extractPathAfterDomain(url: string): string {
+ const parsedUrl = new URL(url);
+ return parsedUrl.pathname.replace(/^\/+/, '');
+}
diff --git a/src/app/features/admin-institutions/helpers/index.ts b/src/app/features/admin-institutions/helpers/index.ts
new file mode 100644
index 000000000..779fca02f
--- /dev/null
+++ b/src/app/features/admin-institutions/helpers/index.ts
@@ -0,0 +1 @@
+export * from './extract-path-after-domain';
diff --git a/src/app/features/admin-institutions/mappers/index.ts b/src/app/features/admin-institutions/mappers/index.ts
index 20dbd3dfa..ea84875b8 100644
--- a/src/app/features/admin-institutions/mappers/index.ts
+++ b/src/app/features/admin-institutions/mappers/index.ts
@@ -1,6 +1,9 @@
export { mapInstitutionDepartment, mapInstitutionDepartments } from './institution-departments.mapper';
+export { mapPreprintToTableData } from './institution-preprint-to-table-data.mapper';
export { mapProjectToTableCellData } from './institution-project-to-table-data.mapper';
export { mapInstitutionProjects } from './institution-projects.mapper';
+export { mapRegistrationToTableData } from './institution-registration-to-table-data.mapper';
+export { mapInstitutionRegistrations } from './institution-registrations.mapper';
export { mapIndexCardResults } from './institution-summary-index.mapper';
export { mapInstitutionSummaryMetrics } from './institution-summary-metrics.mapper';
export { mapUserToTableCellData } from './institution-user-to-table-data.mapper';
diff --git a/src/app/features/admin-institutions/mappers/institution-preprint-to-table-data.mapper.ts b/src/app/features/admin-institutions/mappers/institution-preprint-to-table-data.mapper.ts
new file mode 100644
index 000000000..de1426c7a
--- /dev/null
+++ b/src/app/features/admin-institutions/mappers/institution-preprint-to-table-data.mapper.ts
@@ -0,0 +1,37 @@
+import { extractPathAfterDomain } from '@osf/features/admin-institutions/helpers';
+
+import { InstitutionPreprint, TableCellData, TableCellLink } from '../models';
+
+export function mapPreprintToTableData(preprint: InstitutionPreprint): TableCellData {
+ return {
+ id: preprint.id,
+ title: {
+ text: preprint.title,
+ url: preprint.link,
+ target: '_blank',
+ } as TableCellLink,
+ link: {
+ text: preprint.link.split('/').pop() || preprint.link,
+ url: preprint.link,
+ target: '_blank',
+ } as TableCellLink,
+ dateCreated: preprint.dateCreated,
+ dateModified: preprint.dateModified,
+ doi: preprint.doi
+ ? ({
+ text: extractPathAfterDomain(preprint.doi),
+ url: preprint.doi,
+ } as TableCellLink)
+ : '-',
+ license: preprint.license || '-',
+ contributorName: preprint.contributorName
+ ? ({
+ text: preprint.contributorName,
+ url: `https://osf.io/${preprint.contributorName}`,
+ target: '_blank',
+ } as TableCellLink)
+ : '-',
+ viewsLast30Days: preprint.viewsLast30Days || '-',
+ downloadsLast30Days: preprint.downloadsLast30Days || '-',
+ };
+}
diff --git a/src/app/features/admin-institutions/mappers/institution-preprints.mapper.ts b/src/app/features/admin-institutions/mappers/institution-preprints.mapper.ts
new file mode 100644
index 000000000..ba9e972f1
--- /dev/null
+++ b/src/app/features/admin-institutions/mappers/institution-preprints.mapper.ts
@@ -0,0 +1,40 @@
+import { IncludedItem, IndexCard, InstitutionPreprint, InstitutionRegistrationsJsonApi, SearchResult } from '../models';
+
+export function mapInstitutionPreprints(response: InstitutionRegistrationsJsonApi): InstitutionPreprint[] {
+ if (!response.included) {
+ return [];
+ }
+
+ const searchResults = response.included.filter(
+ (item: IncludedItem): item is SearchResult => item.type === 'search-result'
+ );
+ const indexCards = response.included.filter((item: IncludedItem): item is IndexCard => item.type === 'index-card');
+
+ const preprints: InstitutionPreprint[] = [];
+
+ searchResults.forEach((result: SearchResult) => {
+ const indexCardId = result.relationships?.indexCard?.data?.id;
+ if (indexCardId) {
+ const indexCard = indexCards.find((card: IndexCard) => card.id === indexCardId);
+ if (indexCard && indexCard.attributes) {
+ const metadata = indexCard.attributes.resourceMetadata;
+
+ if (metadata) {
+ preprints.push({
+ id: metadata['@id'] || indexCard.id,
+ title: metadata.title?.[0]?.['@value'] || '',
+ link: metadata['@id'] || '',
+ dateCreated: metadata.dateCreated?.[0]?.['@value'] || '',
+ dateModified: metadata.dateModified?.[0]?.['@value'] || '',
+ doi: metadata.identifier?.[0]?.['@value'] || '',
+ contributorName: metadata.creator?.[0]?.name?.[0]?.['@value'] || '',
+ license: metadata.rights?.[0]?.name?.[0]?.['@value'] || '',
+ registrationSchema: metadata.subject?.[0]?.prefLabel?.[0]?.['@value'] || '',
+ });
+ }
+ }
+ }
+ });
+
+ return preprints;
+}
diff --git a/src/app/features/admin-institutions/mappers/institution-registration-to-table-data.mapper.ts b/src/app/features/admin-institutions/mappers/institution-registration-to-table-data.mapper.ts
new file mode 100644
index 000000000..1ca7b345d
--- /dev/null
+++ b/src/app/features/admin-institutions/mappers/institution-registration-to-table-data.mapper.ts
@@ -0,0 +1,41 @@
+import { extractPathAfterDomain } from '@osf/features/admin-institutions/helpers';
+
+import { InstitutionRegistration, TableCellData, TableCellLink } from '../models';
+
+export function mapRegistrationToTableData(registration: InstitutionRegistration): TableCellData {
+ return {
+ id: registration.id,
+ title: {
+ text: registration.title,
+ url: registration.link,
+ target: '_blank',
+ } as TableCellLink,
+ link: {
+ text: registration.link.split('/').pop() || registration.link,
+ url: registration.link,
+ target: '_blank',
+ } as TableCellLink,
+ dateCreated: registration.dateCreated,
+ dateModified: registration.dateModified,
+ doi: registration.doi
+ ? ({
+ text: extractPathAfterDomain(registration.doi),
+ url: registration.doi,
+ } as TableCellLink)
+ : '-',
+ storageLocation: registration.storageLocation || '-',
+ totalDataStored: registration.totalDataStored || '-',
+ contributorName: registration.contributorName
+ ? ({
+ text: registration.contributorName,
+ url: `https://osf.io/${registration.contributorName}`,
+ target: '_blank',
+ } as TableCellLink)
+ : '-',
+ views: registration.views || '-',
+ resourceType: registration.resourceType || '-',
+ license: registration.license || '-',
+ funderName: registration.funderName || '-',
+ registrationSchema: registration.registrationSchema || '-',
+ };
+}
diff --git a/src/app/features/admin-institutions/mappers/institution-registrations.mapper.ts b/src/app/features/admin-institutions/mappers/institution-registrations.mapper.ts
new file mode 100644
index 000000000..901f61c68
--- /dev/null
+++ b/src/app/features/admin-institutions/mappers/institution-registrations.mapper.ts
@@ -0,0 +1,57 @@
+import {
+ Affiliation,
+ IncludedItem,
+ IndexCard,
+ InstitutionRegistration,
+ InstitutionRegistrationsJsonApi,
+ SearchResult,
+} from '../models';
+
+export function mapInstitutionRegistrations(response: InstitutionRegistrationsJsonApi): InstitutionRegistration[] {
+ if (!response.included) {
+ return [];
+ }
+
+ const searchResults = response.included.filter(
+ (item: IncludedItem): item is SearchResult => item.type === 'search-result'
+ );
+ const indexCards = response.included.filter((item: IncludedItem): item is IndexCard => item.type === 'index-card');
+ const registrations: InstitutionRegistration[] = [];
+
+ searchResults.forEach((result: SearchResult) => {
+ const indexCardId = result.relationships?.indexCard?.data?.id;
+ if (indexCardId) {
+ const indexCard = indexCards.find((card: IndexCard) => card.id === indexCardId);
+ if (indexCard && indexCard.attributes) {
+ const metadata = indexCard.attributes.resourceMetadata;
+
+ if (metadata) {
+ registrations.push({
+ id: metadata['@id'] || indexCard.id,
+ title: metadata.title?.[0]?.['@value'] || '',
+ link: metadata['@id'] || '',
+ dateCreated: metadata.dateCreated?.[0]?.['@value'] || '',
+ dateModified: metadata.dateModified?.[0]?.['@value'] || '',
+ doi: metadata.identifier?.[0]?.['@value'] || '',
+ storageLocation: metadata.storageRegion?.[0]?.prefLabel?.[0]?.['@value'] || '',
+ totalDataStored: metadata.storageByteCount?.[0]?.['@value'] || '',
+ contributorName: metadata.creator?.[0]?.name?.[0]?.['@value'] || '',
+ views: metadata.usage?.[0]?.viewCount?.[0]?.['@value']
+ ? parseInt(metadata.usage[0].viewCount[0]['@value'])
+ : undefined,
+ resourceType: metadata.resourceType?.[0]?.['@id'] || '',
+ license: metadata.rights?.[0]?.name?.[0]?.['@value'] || '',
+ funderName:
+ metadata.affiliation
+ ?.map((aff: Affiliation) => aff.name?.[0]?.['@value'])
+ .filter((value): value is string => Boolean(value))
+ .join(', ') || '',
+ registrationSchema: metadata.subject?.[0]?.prefLabel?.[0]?.['@value'] || '',
+ });
+ }
+ }
+ }
+ });
+
+ return registrations;
+}
diff --git a/src/app/features/admin-institutions/models/index-search-query-params.model.ts b/src/app/features/admin-institutions/models/index-search-query-params.model.ts
new file mode 100644
index 000000000..e15b990ee
--- /dev/null
+++ b/src/app/features/admin-institutions/models/index-search-query-params.model.ts
@@ -0,0 +1,5 @@
+export interface IndexSearchQueryParamsModel {
+ size?: number;
+ sort?: string;
+ cursor?: string;
+}
diff --git a/src/app/features/admin-institutions/models/index.ts b/src/app/features/admin-institutions/models/index.ts
index c9859791c..b7ddff2c9 100644
--- a/src/app/features/admin-institutions/models/index.ts
+++ b/src/app/features/admin-institutions/models/index.ts
@@ -1,11 +1,15 @@
+export * from './index-search-query-params.model';
export * from './institution-department.model';
export * from './institution-departments-json-api.model';
export * from './institution-index-value-search-json-api.model';
+export * from './institution-preprint.model';
export * from './institution-project.model';
export * from './institution-project.model';
export * from './institution-projects-json-api.model';
export * from './institution-projects-query-params.model';
+export * from './institution-registration.model';
export * from './institution-registrations-json-api.model';
+export * from './institution-registrations-query-params.model';
export * from './institution-search-filter.model';
export * from './institution-summary-metric.model';
export * from './institution-summary-metrics-json-api.model';
diff --git a/src/app/features/admin-institutions/models/institution-preprint.model.ts b/src/app/features/admin-institutions/models/institution-preprint.model.ts
new file mode 100644
index 000000000..ad6d3c410
--- /dev/null
+++ b/src/app/features/admin-institutions/models/institution-preprint.model.ts
@@ -0,0 +1,13 @@
+export interface InstitutionPreprint {
+ id: string;
+ title: string;
+ link: string;
+ dateCreated: string;
+ dateModified: string;
+ doi?: string;
+ license?: string;
+ contributorName: string;
+ viewsLast30Days?: number;
+ downloadsLast30Days?: number;
+ registrationSchema?: string;
+}
diff --git a/src/app/features/admin-institutions/models/institution-registration.model.ts b/src/app/features/admin-institutions/models/institution-registration.model.ts
new file mode 100644
index 000000000..ebf7ceee4
--- /dev/null
+++ b/src/app/features/admin-institutions/models/institution-registration.model.ts
@@ -0,0 +1,16 @@
+export interface InstitutionRegistration {
+ id: string;
+ title: string;
+ link: string;
+ dateCreated: string;
+ dateModified: string;
+ doi?: string;
+ storageLocation: string;
+ totalDataStored?: string;
+ contributorName: string;
+ views?: number;
+ resourceType: string;
+ license?: string;
+ funderName?: string;
+ registrationSchema?: string;
+}
diff --git a/src/app/features/admin-institutions/models/institution-registrations-query-params.model.ts b/src/app/features/admin-institutions/models/institution-registrations-query-params.model.ts
new file mode 100644
index 000000000..96b8d5297
--- /dev/null
+++ b/src/app/features/admin-institutions/models/institution-registrations-query-params.model.ts
@@ -0,0 +1,5 @@
+export interface InstitutionRegistrationsQueryParams {
+ size?: number;
+ cursor?: string;
+ sort?: string;
+}
diff --git a/src/app/features/admin-institutions/pages/institutions-preprints/institutions-preprints.component.html b/src/app/features/admin-institutions/pages/institutions-preprints/institutions-preprints.component.html
index e69de29bb..ddf31efc3 100644
--- a/src/app/features/admin-institutions/pages/institutions-preprints/institutions-preprints.component.html
+++ b/src/app/features/admin-institutions/pages/institutions-preprints/institutions-preprints.component.html
@@ -0,0 +1,26 @@
+@if (isLoading()) {
+
+
+
+} @else if (tableData().length > 0) {
+
+
+
+ {{ totalCount() }} {{ 'adminInstitutions.preprints.totalPreprints' | translate | lowercase }}
+
+
+
+} @else {
+
+
{{ 'adminInstitutions.preprints.noData' | translate }}
+
+}
diff --git a/src/app/features/admin-institutions/pages/institutions-preprints/institutions-preprints.component.scss b/src/app/features/admin-institutions/pages/institutions-preprints/institutions-preprints.component.scss
index e69de29bb..eab134e2c 100644
--- a/src/app/features/admin-institutions/pages/institutions-preprints/institutions-preprints.component.scss
+++ b/src/app/features/admin-institutions/pages/institutions-preprints/institutions-preprints.component.scss
@@ -0,0 +1,3 @@
+.title {
+ color: var(--pr-blue-1);
+}
diff --git a/src/app/features/admin-institutions/pages/institutions-preprints/institutions-preprints.component.ts b/src/app/features/admin-institutions/pages/institutions-preprints/institutions-preprints.component.ts
index 493f5ce8b..1ec7d9b47 100644
--- a/src/app/features/admin-institutions/pages/institutions-preprints/institutions-preprints.component.ts
+++ b/src/app/features/admin-institutions/pages/institutions-preprints/institutions-preprints.component.ts
@@ -1,10 +1,181 @@
-import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { createDispatchMap, select } from '@ngxs/store';
+
+import { TranslatePipe } from '@ngx-translate/core';
+
+import { CommonModule } from '@angular/common';
+import { ChangeDetectionStrategy, Component, computed, effect, inject, signal, untracked } from '@angular/core';
+import { toSignal } from '@angular/core/rxjs-interop';
+import { ActivatedRoute, Params, Router } from '@angular/router';
+
+import { parseQueryFilterParams } from '@core/helpers';
+import { AdminTableComponent } from '@osf/features/admin-institutions/components';
+import { preprintsTableColumns } from '@osf/features/admin-institutions/constants';
+import { mapPreprintToTableData } from '@osf/features/admin-institutions/mappers';
+import {
+ IndexSearchQueryParamsModel,
+ InstitutionProjectsQueryParamsModel,
+ TableCellData,
+} from '@osf/features/admin-institutions/models';
+import { InstitutionsAdminSelectors } from '@osf/features/admin-institutions/store';
+import { LoadingSpinnerComponent } from '@osf/shared/components';
+import { TABLE_PARAMS } from '@shared/constants';
+import { SortOrder } from '@shared/enums';
+import { Institution, QueryParams } from '@shared/models';
+import { InstitutionsSearchSelectors } from '@shared/stores';
+
+import { FetchPreprints } from '../../store/institutions-admin.actions';
+
+import { environment } from 'src/environments/environment';
@Component({
selector: 'osf-institutions-preprints',
- imports: [],
+ imports: [CommonModule, AdminTableComponent, TranslatePipe, LoadingSpinnerComponent],
templateUrl: './institutions-preprints.component.html',
styleUrl: './institutions-preprints.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class InstitutionsPreprintsComponent {}
+export class InstitutionsPreprintsComponent {
+ private readonly router = inject(Router);
+ private readonly route = inject(ActivatedRoute);
+
+ private readonly actions = createDispatchMap({
+ fetchPreprints: FetchPreprints,
+ });
+
+ private institutionId = '';
+
+ institution = select(InstitutionsSearchSelectors.getInstitution);
+ preprints = select(InstitutionsAdminSelectors.getPreprints);
+ totalCount = select(InstitutionsAdminSelectors.getPreprintsTotalCount);
+ isLoading = select(InstitutionsAdminSelectors.getPreprintsLoading);
+ preprintsLinks = select(InstitutionsAdminSelectors.getPreprintsLinks);
+
+ tableColumns = signal(preprintsTableColumns);
+ reportsLink = 'https://drive.google.com/drive/folders/1_aFmeJwLp5xBS3-8clZ4xA9L3UFxdzDd';
+
+ queryParams = toSignal(this.route.queryParams);
+ currentPageSize = signal(TABLE_PARAMS.rows);
+ currentSort = signal('-dateModified');
+ sortField = signal('-dateModified');
+ sortOrder = signal(1);
+
+ currentCursor = signal('');
+
+ tableData = computed(() => {
+ const preprintsData = this.preprints();
+ return preprintsData.map(mapPreprintToTableData) as TableCellData[];
+ });
+
+ downloadLink = computed(() => {
+ const institution = this.institution();
+ const queryParams = this.queryParams();
+
+ if (!institution?.iris?.length) {
+ return '';
+ }
+
+ const institutionIris = institution.iris.join(',');
+ const baseUrl = `${environment.shareDomainUrl}/index-card-search`;
+ let params = new URLSearchParams();
+ if (queryParams) {
+ params = new URLSearchParams({
+ 'cardSearchFilter[affiliation][]': institutionIris,
+ 'cardSearchFilter[resourceType]': 'Preprint',
+ 'cardSearchFilter[accessService]': environment.webUrl,
+ 'page[size]': String(queryParams['size'] || this.currentPageSize()),
+ sort: queryParams['sort'] || this.currentSort(),
+ });
+ }
+
+ if (queryParams && queryParams['cursor']) {
+ params.append('page[cursor]', queryParams['cursor']);
+ }
+
+ return `${baseUrl}?${params.toString()}`;
+ });
+
+ constructor() {
+ this.setupQueryParamsEffect();
+ }
+
+ onSortChange(params: QueryParams): void {
+ this.updateQueryParams({
+ sort:
+ params.sortColumn && params.sortOrder
+ ? params.sortOrder === SortOrder.Desc
+ ? `-${params.sortColumn}`
+ : params.sortColumn
+ : undefined,
+ });
+ }
+
+ onLinkPageChange(link: string): void {
+ const url = new URL(link);
+ const cursor = url.searchParams.get('page[cursor]') || '';
+ this.updateQueryParams({ cursor });
+ }
+
+ private setupQueryParamsEffect(): void {
+ effect(() => {
+ const institutionId = this.route.parent?.snapshot.params['institution-id'];
+ const rawQueryParams = this.queryParams();
+ if (!rawQueryParams && !institutionId) return;
+
+ this.institutionId = institutionId;
+ const parsedQueryParams = this.parseQueryParams(rawQueryParams as Params);
+
+ this.updateComponentState(parsedQueryParams);
+
+ const sortField = parsedQueryParams.sortColumn;
+ const sortOrder = parsedQueryParams.sortOrder;
+ const sortParam = sortOrder === SortOrder.Desc ? `-${sortField}` : sortField;
+ const cursor = parsedQueryParams.cursor;
+ const size = parsedQueryParams.size;
+
+ const institution = this.institution() as Institution;
+ const institutionIris = institution.iris || [];
+
+ this.actions.fetchPreprints(this.institutionId, institutionIris, size, sortParam, cursor);
+ });
+ }
+
+ private parseQueryParams(params: Params): InstitutionProjectsQueryParamsModel {
+ const parsed = parseQueryFilterParams(params);
+ return {
+ ...parsed,
+ cursor: params['cursor'] || '',
+ };
+ }
+
+ private updateComponentState(params: InstitutionProjectsQueryParamsModel): void {
+ untracked(() => {
+ this.currentPageSize.set(params.size);
+
+ if (params.sortColumn) {
+ this.sortField.set(params.sortColumn);
+ const order = params.sortOrder === SortOrder.Desc ? -1 : 1;
+ this.sortOrder.set(order);
+ }
+ });
+ }
+
+ private updateQueryParams(params: IndexSearchQueryParamsModel): void {
+ const queryParams: Record = {};
+
+ if (params.sort) {
+ queryParams['sort'] = params.sort;
+ }
+ if (params.cursor) {
+ queryParams['cursor'] = params.cursor;
+ }
+ if (params.size) {
+ queryParams['size'] = params.size.toString();
+ }
+
+ this.router.navigate([], {
+ relativeTo: this.route,
+ queryParams,
+ queryParamsHandling: 'merge',
+ });
+ }
+}
diff --git a/src/app/features/admin-institutions/pages/institutions-projects/institutions-projects.component.html b/src/app/features/admin-institutions/pages/institutions-projects/institutions-projects.component.html
index 7924855a6..85b3ad42c 100644
--- a/src/app/features/admin-institutions/pages/institutions-projects/institutions-projects.component.html
+++ b/src/app/features/admin-institutions/pages/institutions-projects/institutions-projects.component.html
@@ -1,21 +1,30 @@
-
-
-
{{ totalCount() }} {{ 'adminInstitutions.projects.totalProjects' | translate }}
+@if (isLoading()) {
+
+
-
+} @else if (tableData().length > 0) {
+
+
+
{{ totalCount() }} {{ 'adminInstitutions.projects.totalProjects' | translate }}
+
+
+} @else {
+
+
{{ 'adminInstitutions.projects.noData' | translate }}
+
+}
diff --git a/src/app/features/admin-institutions/pages/institutions-projects/institutions-projects.component.ts b/src/app/features/admin-institutions/pages/institutions-projects/institutions-projects.component.ts
index ba83d069d..778003d75 100644
--- a/src/app/features/admin-institutions/pages/institutions-projects/institutions-projects.component.ts
+++ b/src/app/features/admin-institutions/pages/institutions-projects/institutions-projects.component.ts
@@ -12,6 +12,7 @@ import { projectTableColumns } from '@osf/features/admin-institutions/constants'
import { mapProjectToTableCellData } from '@osf/features/admin-institutions/mappers';
import { FetchProjects } from '@osf/features/admin-institutions/store/institutions-admin.actions';
import { InstitutionsAdminSelectors } from '@osf/features/admin-institutions/store/institutions-admin.selectors';
+import { LoadingSpinnerComponent } from '@osf/shared/components';
import { TABLE_PARAMS } from '@shared/constants';
import { SortOrder } from '@shared/enums';
import { Institution, QueryParams } from '@shared/models';
@@ -23,7 +24,7 @@ import { environment } from 'src/environments/environment';
@Component({
selector: 'osf-institutions-projects',
- imports: [AdminTableComponent, TranslatePipe],
+ imports: [AdminTableComponent, TranslatePipe, LoadingSpinnerComponent],
templateUrl: './institutions-projects.component.html',
styleUrl: './institutions-projects.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -40,7 +41,6 @@ export class InstitutionsProjectsComponent {
reportsLink = 'https://drive.google.com/drive/folders/1_aFmeJwLp5xBS3-8clZ4xA9L3UFxdzDd';
queryParams = toSignal(this.route.queryParams);
- currentPage = signal(1);
currentPageSize = signal(TABLE_PARAMS.rows);
first = signal(0);
@@ -175,7 +175,6 @@ export class InstitutionsProjectsComponent {
private updateComponentState(params: InstitutionProjectsQueryParamsModel): void {
untracked(() => {
- this.currentPage.set(params.page);
this.currentPageSize.set(params.size);
this.first.set((params.page - 1) * params.size);
diff --git a/src/app/features/admin-institutions/pages/institutions-registrations/institutions-registrations.component.html b/src/app/features/admin-institutions/pages/institutions-registrations/institutions-registrations.component.html
index e69de29bb..586d48b27 100644
--- a/src/app/features/admin-institutions/pages/institutions-registrations/institutions-registrations.component.html
+++ b/src/app/features/admin-institutions/pages/institutions-registrations/institutions-registrations.component.html
@@ -0,0 +1,26 @@
+@if (isLoading()) {
+
+
+
+} @else if (tableData().length > 0) {
+
+
+
+ {{ totalCount() }} {{ 'adminInstitutions.registrations.totalRegistrations' | translate | lowercase }}
+
+
+
+} @else {
+
+
{{ 'adminInstitutions.registrations.noData' | translate }}
+
+}
diff --git a/src/app/features/admin-institutions/pages/institutions-registrations/institutions-registrations.component.scss b/src/app/features/admin-institutions/pages/institutions-registrations/institutions-registrations.component.scss
index e69de29bb..eab134e2c 100644
--- a/src/app/features/admin-institutions/pages/institutions-registrations/institutions-registrations.component.scss
+++ b/src/app/features/admin-institutions/pages/institutions-registrations/institutions-registrations.component.scss
@@ -0,0 +1,3 @@
+.title {
+ color: var(--pr-blue-1);
+}
diff --git a/src/app/features/admin-institutions/pages/institutions-registrations/institutions-registrations.component.ts b/src/app/features/admin-institutions/pages/institutions-registrations/institutions-registrations.component.ts
index 5dbe432ab..1fff480de 100644
--- a/src/app/features/admin-institutions/pages/institutions-registrations/institutions-registrations.component.ts
+++ b/src/app/features/admin-institutions/pages/institutions-registrations/institutions-registrations.component.ts
@@ -1,10 +1,179 @@
-import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { createDispatchMap, select } from '@ngxs/store';
+
+import { TranslatePipe } from '@ngx-translate/core';
+
+import { CommonModule } from '@angular/common';
+import { ChangeDetectionStrategy, Component, computed, effect, inject, signal, untracked } from '@angular/core';
+import { toSignal } from '@angular/core/rxjs-interop';
+import { ActivatedRoute, Params, Router } from '@angular/router';
+
+import { parseQueryFilterParams } from '@core/helpers';
+import { AdminTableComponent } from '@osf/features/admin-institutions/components';
+import { registrationTableColumns } from '@osf/features/admin-institutions/constants';
+import { mapRegistrationToTableData } from '@osf/features/admin-institutions/mappers';
+import {
+ IndexSearchQueryParamsModel,
+ InstitutionProjectsQueryParamsModel,
+ TableCellData,
+} from '@osf/features/admin-institutions/models';
+import { InstitutionsAdminSelectors } from '@osf/features/admin-institutions/store';
+import { LoadingSpinnerComponent } from '@osf/shared/components';
+import { TABLE_PARAMS } from '@shared/constants';
+import { SortOrder } from '@shared/enums';
+import { Institution, QueryParams } from '@shared/models';
+import { InstitutionsSearchSelectors } from '@shared/stores';
+
+import { FetchRegistrations } from '../../store/institutions-admin.actions';
+
+import { environment } from 'src/environments/environment';
@Component({
selector: 'osf-institutions-registrations',
- imports: [],
+ imports: [CommonModule, AdminTableComponent, TranslatePipe, LoadingSpinnerComponent],
templateUrl: './institutions-registrations.component.html',
styleUrl: './institutions-registrations.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class InstitutionsRegistrationsComponent {}
+export class InstitutionsRegistrationsComponent {
+ private readonly router = inject(Router);
+ private readonly route = inject(ActivatedRoute);
+
+ private readonly actions = createDispatchMap({
+ fetchRegistrations: FetchRegistrations,
+ });
+
+ private institutionId = '';
+
+ institution = select(InstitutionsSearchSelectors.getInstitution);
+ registrations = select(InstitutionsAdminSelectors.getRegistrations);
+ totalCount = select(InstitutionsAdminSelectors.getRegistrationsTotalCount);
+ isLoading = select(InstitutionsAdminSelectors.getRegistrationsLoading);
+ registrationsLinks = select(InstitutionsAdminSelectors.getRegistrationsLinks);
+
+ tableColumns = signal(registrationTableColumns);
+ reportsLink = 'https://drive.google.com/drive/folders/1_aFmeJwLp5xBS3-8clZ4xA9L3UFxdzDd';
+
+ queryParams = toSignal(this.route.queryParams);
+ currentPageSize = signal(TABLE_PARAMS.rows);
+ currentSort = signal('-dateModified');
+ sortField = signal
('-dateModified');
+ sortOrder = signal(1);
+
+ tableData = computed(() => {
+ const registrationsData = this.registrations();
+ return registrationsData.map(mapRegistrationToTableData) as TableCellData[];
+ });
+
+ downloadLink = computed(() => {
+ const institution = this.institution();
+ const queryParams = this.queryParams();
+
+ if (!institution?.iris?.length) {
+ return '';
+ }
+
+ const institutionIris = institution.iris.join(',');
+ const baseUrl = `${environment.shareDomainUrl}/index-card-search`;
+ let params = new URLSearchParams();
+ if (queryParams) {
+ params = new URLSearchParams({
+ 'cardSearchFilter[affiliation][]': institutionIris,
+ 'cardSearchFilter[resourceType]': 'Registration',
+ 'cardSearchFilter[accessService]': environment.webUrl,
+ 'page[size]': String(queryParams['size'] || this.currentPageSize()),
+ sort: queryParams['sort'] || this.currentSort(),
+ });
+ }
+
+ if (queryParams && queryParams['cursor']) {
+ params.append('page[cursor]', queryParams['cursor']);
+ }
+
+ return `${baseUrl}?${params.toString()}`;
+ });
+
+ constructor() {
+ this.setupQueryParamsEffect();
+ }
+
+ onSortChange(params: QueryParams): void {
+ this.updateQueryParams({
+ sort:
+ params.sortColumn && params.sortOrder
+ ? params.sortOrder === SortOrder.Desc
+ ? `-${params.sortColumn}`
+ : params.sortColumn
+ : undefined,
+ });
+ }
+
+ onLinkPageChange(link: string): void {
+ const url = new URL(link);
+ const cursor = url.searchParams.get('page[cursor]') || '';
+ this.updateQueryParams({ cursor });
+ }
+
+ private setupQueryParamsEffect(): void {
+ effect(() => {
+ const institutionId = this.route.parent?.snapshot.params['institution-id'];
+ const rawQueryParams = this.queryParams();
+ if (!rawQueryParams && !institutionId) return;
+
+ this.institutionId = institutionId;
+ const parsedQueryParams = this.parseQueryParams(rawQueryParams as Params);
+
+ this.updateComponentState(parsedQueryParams);
+
+ const sortField = parsedQueryParams.sortColumn;
+ const sortOrder = parsedQueryParams.sortOrder;
+ const sortParam = sortOrder === SortOrder.Desc ? `-${sortField}` : sortField;
+ const cursor = parsedQueryParams.cursor;
+ const size = parsedQueryParams.size;
+
+ const institution = this.institution() as Institution;
+ const institutionIris = institution.iris || [];
+
+ this.actions.fetchRegistrations(this.institutionId, institutionIris, size, sortParam, cursor);
+ });
+ }
+
+ private parseQueryParams(params: Params): InstitutionProjectsQueryParamsModel {
+ const parsed = parseQueryFilterParams(params);
+ return {
+ ...parsed,
+ cursor: params['cursor'] || '',
+ };
+ }
+
+ private updateComponentState(params: InstitutionProjectsQueryParamsModel): void {
+ untracked(() => {
+ this.currentPageSize.set(params.size);
+
+ if (params.sortColumn) {
+ this.sortField.set(params.sortColumn);
+ const order = params.sortOrder === SortOrder.Desc ? -1 : 1;
+ this.sortOrder.set(order);
+ }
+ });
+ }
+
+ private updateQueryParams(params: IndexSearchQueryParamsModel): void {
+ const queryParams: Record = {};
+
+ if (params.sort) {
+ queryParams['sort'] = params.sort;
+ }
+ if (params.cursor) {
+ queryParams['cursor'] = params.cursor;
+ }
+ if (params.size) {
+ queryParams['size'] = params.size.toString();
+ }
+
+ this.router.navigate([], {
+ relativeTo: this.route,
+ queryParams,
+ queryParamsHandling: 'merge',
+ });
+ }
+}
diff --git a/src/app/features/admin-institutions/pages/institutions-users/institutions-users.component.html b/src/app/features/admin-institutions/pages/institutions-users/institutions-users.component.html
index 8710a5fa3..cd2a4694b 100644
--- a/src/app/features/admin-institutions/pages/institutions-users/institutions-users.component.html
+++ b/src/app/features/admin-institutions/pages/institutions-users/institutions-users.component.html
@@ -1,45 +1,55 @@
-
-
-
{{ amountText() }}
+@if (isLoading()) {
+
+
-
-
-
-
-
-
+} @else if (tableData().length > 0) {
+
+
+
{{ amountText() }}
-
-
+
+
+
+
+
+
+
+
+
+
+
+} @else {
+
+
{{ 'adminInstitutions.institutionUsers.noData' | translate }}
-
+}
diff --git a/src/app/features/admin-institutions/pages/institutions-users/institutions-users.component.ts b/src/app/features/admin-institutions/pages/institutions-users/institutions-users.component.ts
index c93d770ba..f6f6e2618 100644
--- a/src/app/features/admin-institutions/pages/institutions-users/institutions-users.component.ts
+++ b/src/app/features/admin-institutions/pages/institutions-users/institutions-users.component.ts
@@ -35,7 +35,7 @@ import {
SendUserMessage,
} from '@osf/features/admin-institutions/store/institutions-admin.actions';
import { InstitutionsAdminSelectors } from '@osf/features/admin-institutions/store/institutions-admin.selectors';
-import { SelectComponent } from '@osf/shared/components';
+import { LoadingSpinnerComponent, SelectComponent } from '@osf/shared/components';
import { TABLE_PARAMS } from '@shared/constants';
import { SortOrder } from '@shared/enums';
import { QueryParams } from '@shared/models';
@@ -51,7 +51,7 @@ import {
@Component({
selector: 'osf-institutions-users',
- imports: [AdminTableComponent, FormsModule, SelectComponent, CheckboxModule, TranslatePipe],
+ imports: [AdminTableComponent, FormsModule, SelectComponent, CheckboxModule, TranslatePipe, LoadingSpinnerComponent],
templateUrl: './institutions-users.component.html',
styleUrl: './institutions-users.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
diff --git a/src/app/features/admin-institutions/services/institutions-admin.service.ts b/src/app/features/admin-institutions/services/institutions-admin.service.ts
index 9dd5eedb2..80a69cbfd 100644
--- a/src/app/features/admin-institutions/services/institutions-admin.service.ts
+++ b/src/app/features/admin-institutions/services/institutions-admin.service.ts
@@ -4,6 +4,7 @@ import { map } from 'rxjs/operators';
import { inject, Injectable } from '@angular/core';
import { JsonApiService } from '@core/services';
+import { mapInstitutionPreprints } from '@osf/features/admin-institutions/mappers/institution-preprints.mapper';
import { departmens, summaryMetrics, users } from '@osf/features/admin-institutions/services/mock';
import { PaginationLinksModel } from '@shared/models';
@@ -11,6 +12,7 @@ import {
mapIndexCardResults,
mapInstitutionDepartments,
mapInstitutionProjects,
+ mapInstitutionRegistrations,
mapInstitutionSummaryMetrics,
mapInstitutionUsers,
sendMessageRequestMapper,
@@ -19,7 +21,9 @@ import {
InstitutionDepartment,
InstitutionDepartmentsJsonApi,
InstitutionIndexValueSearchJsonApi,
+ InstitutionPreprint,
InstitutionProject,
+ InstitutionRegistration,
InstitutionRegistrationsJsonApi,
InstitutionSearchFilter,
InstitutionSummaryMetrics,
@@ -92,42 +96,16 @@ export class InstitutionsAdminService {
);
}
- fetchProjects(
- institutionId: string,
- institutionIris: string[],
- pageSize = 10,
- sort = '-dateModified',
- cursor = ''
- ): Observable<{
- projects: InstitutionProject[];
- totalCount: number;
- links?: PaginationLinksModel;
- }> {
- const url = `${environment.shareDomainUrl}/index-card-search`;
- let params: Record
= {};
-
- const affiliationParam = institutionIris.join(',');
+ fetchProjects(institutionId: string, iris: string[], pageSize = 10, sort = '-dateModified', cursor = '') {
+ return this.fetchIndexCards('Project', iris, pageSize, sort, cursor);
+ }
- params = {
- 'cardSearchFilter[affiliation][]': affiliationParam,
- 'cardSearchFilter[resourceType]': 'Project',
- 'cardSearchFilter[accessService]': environment.webUrl,
- 'page[cursor]': cursor,
- 'page[size]': pageSize.toString(),
- sort,
- };
+ fetchRegistrations(institutionId: string, iris: string[], pageSize = 10, sort = '-dateModified', cursor = '') {
+ return this.fetchIndexCards('Registration', iris, pageSize, sort, cursor);
+ }
- return this.jsonApiService.get(url, params).pipe(
- map((response: InstitutionRegistrationsJsonApi) => {
- const projects = mapInstitutionProjects(response);
- const links = response.data.relationships.searchResultPage.links;
- return {
- projects,
- totalCount: response.data.attributes.totalResultCount,
- links,
- };
- })
- );
+ fetchPreprints(institutionId: string, iris: string[], pageSize = 10, sort = '-dateModified', cursor = '') {
+ return this.fetchIndexCards('Preprint', iris, pageSize, sort, cursor);
}
fetchIndexValueSearch(
@@ -153,4 +131,53 @@ export class InstitutionsAdminService {
return this.jsonApiService.post(`${this.hardcodedUrl}/institutions/messages/`, payload);
}
+
+ private fetchIndexCards(
+ resourceType: 'Project' | 'Registration' | 'Preprint',
+ institutionIris: string[],
+ pageSize = 10,
+ sort = '-dateModified',
+ cursor = ''
+ ): Observable<{
+ items: InstitutionProject[] | InstitutionRegistration[] | InstitutionPreprint[];
+ totalCount: number;
+ links?: PaginationLinksModel;
+ }> {
+ const url = `${environment.shareDomainUrl}/index-card-search`;
+ const affiliationParam = institutionIris.join(',');
+
+ const params: Record = {
+ 'cardSearchFilter[affiliation][]': affiliationParam,
+ 'cardSearchFilter[resourceType]': resourceType,
+ 'cardSearchFilter[accessService]': environment.webUrl,
+ 'page[cursor]': cursor,
+ 'page[size]': pageSize.toString(),
+ sort,
+ };
+
+ return this.jsonApiService.get(url, params).pipe(
+ map((res) => {
+ let mapper: (
+ response: InstitutionRegistrationsJsonApi
+ ) => InstitutionProject[] | InstitutionRegistration[] | InstitutionPreprint[];
+ switch (resourceType) {
+ case 'Registration':
+ mapper = mapInstitutionRegistrations;
+ break;
+ case 'Project':
+ mapper = mapInstitutionProjects;
+ break;
+ default:
+ mapper = mapInstitutionPreprints;
+ break;
+ }
+
+ return {
+ items: mapper(res),
+ totalCount: res.data.attributes.totalResultCount,
+ links: res.data.relationships.searchResultPage.links,
+ };
+ })
+ );
+ }
}
diff --git a/src/app/features/admin-institutions/store/institutions-admin.actions.ts b/src/app/features/admin-institutions/store/institutions-admin.actions.ts
index a44bd7457..0d74548e7 100644
--- a/src/app/features/admin-institutions/store/institutions-admin.actions.ts
+++ b/src/app/features/admin-institutions/store/institutions-admin.actions.ts
@@ -49,6 +49,28 @@ export class FetchProjects {
) {}
}
+export class FetchRegistrations {
+ static readonly type = '[InstitutionsAdmin] Fetch Registrations';
+ constructor(
+ public institutionId: string,
+ public institutionIris: string[],
+ public pageSize = 10,
+ public sort = '-dateModified',
+ public cursor = ''
+ ) {}
+}
+
+export class FetchPreprints {
+ static readonly type = '[InstitutionsAdmin] Fetch Preprints';
+ constructor(
+ public institutionId: string,
+ public institutionIris: string[],
+ public pageSize = 10,
+ public sort = '-dateModified',
+ public cursor = ''
+ ) {}
+}
+
export class SendUserMessage {
static readonly type = '[InstitutionsAdmin] Send User Message';
constructor(
diff --git a/src/app/features/admin-institutions/store/institutions-admin.model.ts b/src/app/features/admin-institutions/store/institutions-admin.model.ts
index 473fcbd94..fc3588d2b 100644
--- a/src/app/features/admin-institutions/store/institutions-admin.model.ts
+++ b/src/app/features/admin-institutions/store/institutions-admin.model.ts
@@ -2,7 +2,9 @@ import { AsyncStateModel, AsyncStateWithLinksModel, AsyncStateWithTotalCount } f
import {
InstitutionDepartment,
+ InstitutionPreprint,
InstitutionProject,
+ InstitutionRegistration,
InstitutionSearchFilter,
InstitutionSummaryMetrics,
InstitutionUser,
@@ -17,6 +19,8 @@ export interface InstitutionsAdminModel {
searchResults: AsyncStateModel;
users: AsyncStateWithTotalCount;
projects: AsyncStateWithLinksModel;
+ registrations: AsyncStateWithLinksModel;
+ preprints: AsyncStateWithLinksModel;
sendMessage: AsyncStateModel;
selectedInstitutionId: string | null;
currentSearchPropertyPath: string | null;
diff --git a/src/app/features/admin-institutions/store/institutions-admin.selectors.ts b/src/app/features/admin-institutions/store/institutions-admin.selectors.ts
index 91959090f..6ab035c26 100644
--- a/src/app/features/admin-institutions/store/institutions-admin.selectors.ts
+++ b/src/app/features/admin-institutions/store/institutions-admin.selectors.ts
@@ -4,7 +4,9 @@ import { PaginationLinksModel } from '@shared/models';
import {
InstitutionDepartment,
+ InstitutionPreprint,
InstitutionProject,
+ InstitutionRegistration,
InstitutionSearchFilter,
InstitutionSummaryMetrics,
InstitutionUser,
@@ -140,6 +142,46 @@ export class InstitutionsAdminSelectors {
return state.projects.links;
}
+ @Selector([InstitutionsAdminState])
+ static getRegistrations(state: InstitutionsAdminModel): InstitutionRegistration[] {
+ return state.registrations.data;
+ }
+
+ @Selector([InstitutionsAdminState])
+ static getRegistrationsLoading(state: InstitutionsAdminModel): boolean {
+ return state.registrations.isLoading;
+ }
+
+ @Selector([InstitutionsAdminState])
+ static getRegistrationsTotalCount(state: InstitutionsAdminModel): number {
+ return state.registrations.totalCount;
+ }
+
+ @Selector([InstitutionsAdminState])
+ static getRegistrationsLinks(state: InstitutionsAdminModel): PaginationLinksModel | undefined {
+ return state.registrations.links;
+ }
+
+ @Selector([InstitutionsAdminState])
+ static getPreprints(state: InstitutionsAdminModel): InstitutionPreprint[] {
+ return state.preprints.data;
+ }
+
+ @Selector([InstitutionsAdminState])
+ static getPreprintsLoading(state: InstitutionsAdminModel): boolean {
+ return state.preprints.isLoading;
+ }
+
+ @Selector([InstitutionsAdminState])
+ static getPreprintsTotalCount(state: InstitutionsAdminModel): number {
+ return state.preprints.totalCount;
+ }
+
+ @Selector([InstitutionsAdminState])
+ static getPreprintsLinks(state: InstitutionsAdminModel): PaginationLinksModel | undefined {
+ return state.preprints.links;
+ }
+
@Selector([InstitutionsAdminState])
static getSendMessageResponse(state: InstitutionsAdminModel): SendMessageResponseJsonApi | null {
return state.sendMessage.data;
diff --git a/src/app/features/admin-institutions/store/institutions-admin.state.ts b/src/app/features/admin-institutions/store/institutions-admin.state.ts
index dfb3b636f..312a54c52 100644
--- a/src/app/features/admin-institutions/store/institutions-admin.state.ts
+++ b/src/app/features/admin-institutions/store/institutions-admin.state.ts
@@ -6,7 +6,7 @@ import { inject, Injectable } from '@angular/core';
import { handleSectionError } from '@core/handlers';
-import { InstitutionSummaryMetrics } from '../models';
+import { InstitutionPreprint, InstitutionProject, InstitutionRegistration, InstitutionSummaryMetrics } from '../models';
import { InstitutionsAdminService } from '../services/institutions-admin.service';
import {
@@ -15,7 +15,9 @@ import {
FetchInstitutionSearchResults,
FetchInstitutionSummaryMetrics,
FetchInstitutionUsers,
+ FetchPreprints,
FetchProjects,
+ FetchRegistrations,
FetchStorageRegionSearch,
SendUserMessage,
} from './institutions-admin.actions';
@@ -31,6 +33,8 @@ import { InstitutionsAdminModel } from './institutions-admin.model';
searchResults: { data: [], isLoading: false, error: null },
users: { data: [], totalCount: 0, isLoading: false, error: null },
projects: { data: [], totalCount: 0, isLoading: false, error: null, links: undefined },
+ registrations: { data: [], totalCount: 0, isLoading: false, error: null, links: undefined },
+ preprints: { data: [], totalCount: 0, isLoading: false, error: null, links: undefined },
sendMessage: { data: null, isLoading: false, error: null },
selectedInstitutionId: null,
currentSearchPropertyPath: null,
@@ -161,7 +165,7 @@ export class InstitutionsAdminState {
tap((response) => {
ctx.patchState({
projects: {
- data: response.projects,
+ data: response.items as InstitutionProject[],
totalCount: response.totalCount,
isLoading: false,
error: null,
@@ -173,6 +177,56 @@ export class InstitutionsAdminState {
);
}
+ @Action(FetchRegistrations)
+ fetchRegistrations(ctx: StateContext, action: FetchRegistrations) {
+ const state = ctx.getState();
+ ctx.patchState({
+ registrations: { ...state.registrations, isLoading: true, error: null },
+ });
+
+ return this.institutionsAdminService
+ .fetchRegistrations(action.institutionId, action.institutionIris, action.pageSize, action.sort, action.cursor)
+ .pipe(
+ tap((response) => {
+ ctx.patchState({
+ registrations: {
+ data: response.items as InstitutionRegistration[],
+ totalCount: response.totalCount,
+ isLoading: false,
+ error: null,
+ links: response.links,
+ },
+ });
+ }),
+ catchError((error) => handleSectionError(ctx, 'registrations', error))
+ );
+ }
+
+ @Action(FetchPreprints)
+ fetchPreprints(ctx: StateContext, action: FetchPreprints) {
+ const state = ctx.getState();
+ ctx.patchState({
+ preprints: { ...state.preprints, isLoading: true, error: null },
+ });
+
+ return this.institutionsAdminService
+ .fetchPreprints(action.institutionId, action.institutionIris, action.pageSize, action.sort, action.cursor)
+ .pipe(
+ tap((response) => {
+ ctx.patchState({
+ preprints: {
+ data: response.items as InstitutionPreprint[],
+ totalCount: response.totalCount,
+ isLoading: false,
+ error: null,
+ links: response.links,
+ },
+ });
+ }),
+ catchError((error) => handleSectionError(ctx, 'preprints', error))
+ );
+ }
+
@Action(SendUserMessage)
sendUserMessage(ctx: StateContext, action: SendUserMessage) {
const state = ctx.getState();
diff --git a/src/app/features/institutions/institutions.component.scss b/src/app/features/institutions/institutions.component.scss
new file mode 100644
index 000000000..5f81e6c60
--- /dev/null
+++ b/src/app/features/institutions/institutions.component.scss
@@ -0,0 +1,3 @@
+:host {
+ flex: 1;
+}
diff --git a/src/app/features/institutions/institutions.component.ts b/src/app/features/institutions/institutions.component.ts
index 0d0687196..f89bfbc89 100644
--- a/src/app/features/institutions/institutions.component.ts
+++ b/src/app/features/institutions/institutions.component.ts
@@ -5,6 +5,7 @@ import { RouterOutlet } from '@angular/router';
selector: 'osf-institutions',
imports: [RouterOutlet],
templateUrl: './institutions.component.html',
+ styleUrl: './institutions.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class InstitutionsComponent {}
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index cdc50bc64..56a20cb47 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -2183,7 +2183,8 @@
"hasOrcid": "Has ORCID",
"sendMessage": "Send message",
"osfLink": "OSF Link",
- "orcid": "ORCID"
+ "orcid": "ORCID",
+ "noData": "No users found"
},
"projects": {
"title": "Title",
@@ -2199,7 +2200,19 @@
"license": "License",
"addOns": "Add-ons",
"funderName": "Funder Name",
- "totalProjects": "Total Projects"
+ "totalProjects": "Total Projects",
+ "noData": "No projects found"
+ },
+ "registrations": {
+ "funderName": "Funder Name",
+ "registrationSchema": "Registration Schema",
+ "totalRegistrations": "Total Registrations",
+ "noData": "No registrations found"
+ },
+ "preprints": {
+ "totalPreprints": "Total Preprints",
+ "downloadsLastDays": "Downloads (last 30 days)",
+ "noData": "No preprints found"
}
}
}