From 0e24fb270b9f47fba62a7b299ee7240ea18e46b6 Mon Sep 17 00:00:00 2001 From: volodyayakubovskyy Date: Mon, 23 Jun 2025 10:18:15 +0300 Subject: [PATCH 1/3] feat(institutions): add institutions list page --- src/app/app.routes.ts | 4 + src/app/core/constants/nav-items.constant.ts | 7 ++ .../core/constants/ngxs-states.constant.ts | 2 +- src/app/features/home/home.component.ts | 4 +- .../institutions/institutions.component.html | 28 +++++ .../institutions/institutions.component.scss | 0 .../institutions.component.spec.ts | 22 ++++ .../institutions/institutions.component.ts | 111 ++++++++++++++++++ .../institutions/institutions.routes.ts | 10 ++ .../features/institutions/mappers/index.ts | 1 - src/app/features/institutions/models/index.ts | 1 - .../features/institutions/services/index.ts | 1 - .../services/institutions.service.ts | 34 ------ .../store/institutions.actions.ts | 3 - .../institutions/store/institutions.model.ts | 5 - .../store/institutions.selectors.ts | 11 -- .../institutions/store/institutions.state.ts | 32 ----- .../my-projects/my-projects.component.spec.ts | 2 +- .../my-projects/my-projects.component.ts | 2 +- .../store/account-settings.model.ts | 2 +- .../store/account-settings.selectors.ts | 2 +- .../store/account-settings.state.ts | 2 +- .../add-project-form.component.spec.ts | 3 +- .../add-project-form.component.ts | 3 +- .../addon-terms/addon-terms.component.ts | 1 - .../sub-header/sub-header.component.html | 2 +- .../sub-header/sub-header.component.ts | 3 +- src/app/shared/mappers/index.ts | 1 + .../general-institution.mapper.ts | 25 ++++ src/app/shared/mappers/institutions/index.ts | 2 + .../institutions/user-institutions.mapper.ts} | 4 +- src/app/shared/models/index.ts | 1 + src/app/shared/models/institutions/index.ts | 2 + .../institutions-json-api.model.ts | 66 +++++++++++ .../institutions}/institutions.models.ts | 0 src/app/shared/services/index.ts | 1 + .../shared/services/institutions.service.ts | 62 ++++++++++ src/app/shared/stores/index.ts | 2 + .../stores/institutions}/index.ts | 0 .../institutions/institutions.actions.ts | 13 ++ .../stores/institutions/institutions.model.ts | 7 ++ .../institutions/institutions.selectors.ts | 26 ++++ .../stores/institutions/institutions.state.ts | 57 +++++++++ src/assets/i18n/en.json | 9 +- 44 files changed, 472 insertions(+), 104 deletions(-) create mode 100644 src/app/features/institutions/institutions.component.html create mode 100644 src/app/features/institutions/institutions.component.scss create mode 100644 src/app/features/institutions/institutions.component.spec.ts create mode 100644 src/app/features/institutions/institutions.component.ts create mode 100644 src/app/features/institutions/institutions.routes.ts delete mode 100644 src/app/features/institutions/mappers/index.ts delete mode 100644 src/app/features/institutions/models/index.ts delete mode 100644 src/app/features/institutions/services/index.ts delete mode 100644 src/app/features/institutions/services/institutions.service.ts delete mode 100644 src/app/features/institutions/store/institutions.actions.ts delete mode 100644 src/app/features/institutions/store/institutions.model.ts delete mode 100644 src/app/features/institutions/store/institutions.selectors.ts delete mode 100644 src/app/features/institutions/store/institutions.state.ts create mode 100644 src/app/shared/mappers/institutions/general-institution.mapper.ts create mode 100644 src/app/shared/mappers/institutions/index.ts rename src/app/{features/institutions/mappers/institutions.mapper.ts => shared/mappers/institutions/user-institutions.mapper.ts} (83%) create mode 100644 src/app/shared/models/institutions/index.ts create mode 100644 src/app/shared/models/institutions/institutions-json-api.model.ts rename src/app/{features/institutions/models => shared/models/institutions}/institutions.models.ts (100%) create mode 100644 src/app/shared/services/institutions.service.ts create mode 100644 src/app/shared/stores/index.ts rename src/app/{features/institutions/store => shared/stores/institutions}/index.ts (100%) create mode 100644 src/app/shared/stores/institutions/institutions.actions.ts create mode 100644 src/app/shared/stores/institutions/institutions.model.ts create mode 100644 src/app/shared/stores/institutions/institutions.selectors.ts create mode 100644 src/app/shared/stores/institutions/institutions.state.ts diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 683d06f2c..ecf0d9408 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -190,6 +190,10 @@ export const routes: Routes = [ provideStates([MyProfileResourceFiltersState, MyProfileResourceFiltersOptionsState, MyProfileState]), ], }, + { + path: 'institutions', + loadChildren: () => import('./features/institutions/institutions.routes').then((r) => r.routes), + }, { path: 'confirm/:userId/:token', loadComponent: () => import('./features/home/home.component').then((mod) => mod.HomeComponent), diff --git a/src/app/core/constants/nav-items.constant.ts b/src/app/core/constants/nav-items.constant.ts index 5421c6368..960ffa864 100644 --- a/src/app/core/constants/nav-items.constant.ts +++ b/src/app/core/constants/nav-items.constant.ts @@ -53,6 +53,13 @@ export const NAV_ITEMS: NavItem[] = [ icon: 'profile', useExactMatch: true, }, + { + path: '/institutions', + label: 'navigation.institutions', + icon: 'institutions', + useExactMatch: true, + }, + { path: '/collections', label: 'navigation.collections', diff --git a/src/app/core/constants/ngxs-states.constant.ts b/src/app/core/constants/ngxs-states.constant.ts index 7f622dcdd..d04e13eb9 100644 --- a/src/app/core/constants/ngxs-states.constant.ts +++ b/src/app/core/constants/ngxs-states.constant.ts @@ -1,7 +1,6 @@ import { AuthState } from '@core/store/auth'; import { UserState } from '@core/store/user'; import { CollectionsState } from '@osf/features/collections/store'; -import { InstitutionsState } from '@osf/features/institutions/store'; import { MeetingsState } from '@osf/features/meetings/store'; import { MyProjectsState } from '@osf/features/my-projects/store'; import { AnalyticsState } from '@osf/features/project/analytics/store'; @@ -14,6 +13,7 @@ import { DeveloperAppsState } from '@osf/features/settings/developer-apps/store' import { NotificationSubscriptionState } from '@osf/features/settings/notifications/store'; import { ProfileSettingsState } from '@osf/features/settings/profile-settings/store/profile-settings.state'; import { TokensState } from '@osf/features/settings/tokens/store'; +import { InstitutionsState } from '@shared/stores'; import { AddonsState } from '@shared/stores/addons'; export const STATES = [ diff --git a/src/app/features/home/home.component.ts b/src/app/features/home/home.component.ts index a9391fbf3..8215dc56f 100644 --- a/src/app/features/home/home.component.ts +++ b/src/app/features/home/home.component.ts @@ -15,14 +15,14 @@ import { FormControl } from '@angular/forms'; import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { MY_PROJECTS_TABLE_PARAMS } from '@osf/core/constants'; +import { MyProjectsItem } from '@osf/features/my-projects/models'; import { AddProjectFormComponent, MyProjectsTableComponent, SubHeaderComponent } from '@osf/shared/components'; import { SortOrder } from '@osf/shared/enums'; import { TableParameters } from '@osf/shared/models'; import { IS_MEDIUM } from '@osf/shared/utils'; +import { GetUserInstitutions } from '@shared/stores'; -import { GetUserInstitutions } from '../institutions/store'; import { MyProjectsSearchFilters } from '../my-projects/models'; -import { MyProjectsItem } from '../my-projects/models/my-projects.models'; import { ClearMyProjects, GetMyProjects, MyProjectsSelectors } from '../my-projects/store'; import { AccountSettingsService } from '../settings/account-settings/services'; diff --git a/src/app/features/institutions/institutions.component.html b/src/app/features/institutions/institutions.component.html new file mode 100644 index 000000000..64ea61f55 --- /dev/null +++ b/src/app/features/institutions/institutions.component.html @@ -0,0 +1,28 @@ +
+ + +
+ + +
+ @for (institution of institutions(); track $index) { +
+ + +

{{ institution.name }}

+
+ } +
+ + +
+
diff --git a/src/app/features/institutions/institutions.component.scss b/src/app/features/institutions/institutions.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/features/institutions/institutions.component.spec.ts b/src/app/features/institutions/institutions.component.spec.ts new file mode 100644 index 000000000..e8e7def49 --- /dev/null +++ b/src/app/features/institutions/institutions.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InstitutionsComponent } from './institutions.component'; + +describe('InstitutionsComponent', () => { + let component: InstitutionsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [InstitutionsComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(InstitutionsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/institutions/institutions.component.ts b/src/app/features/institutions/institutions.component.ts new file mode 100644 index 000000000..8d525cdeb --- /dev/null +++ b/src/app/features/institutions/institutions.component.ts @@ -0,0 +1,111 @@ +import { createDispatchMap, select } from '@ngxs/store'; + +import { TranslatePipe } from '@ngx-translate/core'; + +import { PaginatorState } from 'primeng/paginator'; + +import { debounceTime, distinctUntilChanged } from 'rxjs'; + +import { NgOptimizedImage } from '@angular/common'; +import { ChangeDetectionStrategy, Component, DestroyRef, effect, inject, signal, untracked } from '@angular/core'; +import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; +import { FormControl } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; + +import { parseQueryFilterParams } from '@core/helpers'; +import { MEETINGS_TABLE_PARAMS } from '@osf/features/meetings/constants'; +import { CustomPaginatorComponent, SearchInputComponent, SubHeaderComponent } from '@shared/components'; +import { QueryParams } from '@shared/models'; +import { GetInstitutions, InstitutionsSelectors } from '@shared/stores'; + +@Component({ + selector: 'osf-institutions', + imports: [SubHeaderComponent, TranslatePipe, SearchInputComponent, NgOptimizedImage, CustomPaginatorComponent], + templateUrl: './institutions.component.html', + styleUrl: './institutions.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class InstitutionsComponent { + private readonly route = inject(ActivatedRoute); + private readonly router = inject(Router); + private readonly destroyRef = inject(DestroyRef); + + private readonly actions = createDispatchMap({ getInstitutions: GetInstitutions }); + + searchControl = new FormControl(''); + + queryParams = toSignal(this.route.queryParams); + currentPage = signal(1); + currentPageSize = signal(MEETINGS_TABLE_PARAMS.rows); + first = signal(0); + + institutions = select(InstitutionsSelectors.getInstitutions); + totalInstitutionsCount = select(InstitutionsSelectors.getInstitutionsTotalCount); + institutionsLoading = select(InstitutionsSelectors.isInstitutionsLoading); + + constructor() { + this.setupQueryParamsEffect(); + this.setupSearchSubscription(); + } + + onPageChange(event: PaginatorState): void { + this.currentPage.set(event.page ? this.currentPage() + 1 : 1); + this.first.set(event.first ?? 0); + this.updateQueryParams({ + page: this.currentPage(), + size: event.rows, + }); + } + + private setupQueryParamsEffect(): void { + effect(() => { + const rawQueryParams = this.queryParams(); + if (!rawQueryParams) return; + + const parsedQueryParams = parseQueryFilterParams(rawQueryParams); + + this.updateComponentState(parsedQueryParams); + + this.actions.getInstitutions(parsedQueryParams.page, parsedQueryParams.size, parsedQueryParams.search); + }); + } + + private updateQueryParams(updates: Partial): void { + const queryParams: Record = {}; + + if ('page' in updates) { + queryParams['page'] = updates.page!.toString(); + } + if ('size' in updates) { + queryParams['size'] = updates.size!.toString(); + } + if ('search' in updates) { + queryParams['search'] = updates.search || undefined; + } + + this.router.navigate([], { + relativeTo: this.route, + queryParams, + queryParamsHandling: 'merge', + }); + } + + private setupSearchSubscription(): void { + this.searchControl.valueChanges + .pipe(debounceTime(300), distinctUntilChanged(), takeUntilDestroyed(this.destroyRef)) + .subscribe((searchControl) => { + this.updateQueryParams({ + search: searchControl ?? '', + page: 1, + }); + }); + } + + private updateComponentState(params: QueryParams): void { + untracked(() => { + this.currentPage.set(params.page); + this.currentPageSize.set(params.size); + this.searchControl.setValue(params.search); + }); + } +} diff --git a/src/app/features/institutions/institutions.routes.ts b/src/app/features/institutions/institutions.routes.ts new file mode 100644 index 000000000..4d506a60d --- /dev/null +++ b/src/app/features/institutions/institutions.routes.ts @@ -0,0 +1,10 @@ +import { Routes } from '@angular/router'; + +import { InstitutionsComponent } from '@osf/features/institutions/institutions.component'; + +export const routes: Routes = [ + { + path: '', + component: InstitutionsComponent, + }, +]; diff --git a/src/app/features/institutions/mappers/index.ts b/src/app/features/institutions/mappers/index.ts deleted file mode 100644 index d43e7a986..000000000 --- a/src/app/features/institutions/mappers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './institutions.mapper'; diff --git a/src/app/features/institutions/models/index.ts b/src/app/features/institutions/models/index.ts deleted file mode 100644 index 5ce1b7814..000000000 --- a/src/app/features/institutions/models/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './institutions.models'; diff --git a/src/app/features/institutions/services/index.ts b/src/app/features/institutions/services/index.ts deleted file mode 100644 index a5a38741c..000000000 --- a/src/app/features/institutions/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { InstitutionsService } from './institutions.service'; diff --git a/src/app/features/institutions/services/institutions.service.ts b/src/app/features/institutions/services/institutions.service.ts deleted file mode 100644 index ad09830e8..000000000 --- a/src/app/features/institutions/services/institutions.service.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; - -import { inject, Injectable } from '@angular/core'; - -import { JsonApiResponse } from '@osf/core/models'; -import { JsonApiService } from '@osf/core/services'; - -import { InstitutionsMapper } from '../mappers'; -import { Institution, UserInstitutionGetResponse } from '../models'; - -import { environment } from 'src/environments/environment'; - -@Injectable({ - providedIn: 'root', -}) -export class InstitutionsService { - #jsonApiService = inject(JsonApiService); - - getUserInstitutions(): Observable { - const url = `${environment.apiUrl}/users/me/institutions/`; - - return this.#jsonApiService - .get>(url) - .pipe(map((response) => response.data.map((item) => InstitutionsMapper.fromResponse(item)))); - } - - deleteUserInstitution(id: string, userId: string): Observable { - const payload = { - data: [{ id: id, type: 'institutions' }], - }; - return this.#jsonApiService.delete(`${environment.apiUrl}/users/${userId}/relationships/institutions/`, payload); - } -} diff --git a/src/app/features/institutions/store/institutions.actions.ts b/src/app/features/institutions/store/institutions.actions.ts deleted file mode 100644 index 2af0d870e..000000000 --- a/src/app/features/institutions/store/institutions.actions.ts +++ /dev/null @@ -1,3 +0,0 @@ -export class GetUserInstitutions { - static readonly type = '[Institutions] Get User Institutions'; -} diff --git a/src/app/features/institutions/store/institutions.model.ts b/src/app/features/institutions/store/institutions.model.ts deleted file mode 100644 index da0e445be..000000000 --- a/src/app/features/institutions/store/institutions.model.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Institution } from '../models'; - -export interface InstitutionsStateModel { - userInstitutions: Institution[]; -} diff --git a/src/app/features/institutions/store/institutions.selectors.ts b/src/app/features/institutions/store/institutions.selectors.ts deleted file mode 100644 index 278497136..000000000 --- a/src/app/features/institutions/store/institutions.selectors.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Selector } from '@ngxs/store'; - -import { InstitutionsStateModel } from './institutions.model'; -import { InstitutionsState } from './institutions.state'; - -export class InstitutionsSelectors { - @Selector([InstitutionsState]) - static getUserInstitutions(state: InstitutionsStateModel) { - return state.userInstitutions; - } -} diff --git a/src/app/features/institutions/store/institutions.state.ts b/src/app/features/institutions/store/institutions.state.ts deleted file mode 100644 index 73649d8d4..000000000 --- a/src/app/features/institutions/store/institutions.state.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Action, State, StateContext } from '@ngxs/store'; - -import { tap } from 'rxjs'; - -import { inject, Injectable } from '@angular/core'; - -import { InstitutionsService } from '../services'; - -import { GetUserInstitutions } from './institutions.actions'; -import { InstitutionsStateModel } from './institutions.model'; - -@State({ - name: 'institutions', - defaults: { - userInstitutions: [], - }, -}) -@Injectable() -export class InstitutionsState { - #institutionsService = inject(InstitutionsService); - - @Action(GetUserInstitutions) - getUserInstitutions(ctx: StateContext) { - return this.#institutionsService.getUserInstitutions().pipe( - tap((institutions) => { - ctx.patchState({ - userInstitutions: institutions, - }); - }) - ); - } -} diff --git a/src/app/features/my-projects/my-projects.component.spec.ts b/src/app/features/my-projects/my-projects.component.spec.ts index d931c879b..0b17f1651 100644 --- a/src/app/features/my-projects/my-projects.component.spec.ts +++ b/src/app/features/my-projects/my-projects.component.spec.ts @@ -14,7 +14,7 @@ import { ActivatedRoute } from '@angular/router'; import { IS_MEDIUM, IS_WEB, IS_XSMALL } from '@shared/utils'; -import { InstitutionsState } from '../institutions/store'; +import { InstitutionsState } from '../../shared/stores/institutions'; import { MyProjectsState } from './store/my-projects.state'; import { MyProjectsComponent } from './my-projects.component'; diff --git a/src/app/features/my-projects/my-projects.component.ts b/src/app/features/my-projects/my-projects.component.ts index 1938c1a8a..bdd37d6d8 100644 --- a/src/app/features/my-projects/my-projects.component.ts +++ b/src/app/features/my-projects/my-projects.component.ts @@ -31,8 +31,8 @@ import { SortOrder } from '@osf/shared/enums'; import { QueryParams, TableParameters, TabOption } from '@osf/shared/models'; import { IS_MEDIUM, IS_WEB, IS_XSMALL } from '@osf/shared/utils'; +import { GetUserInstitutions } from '../../shared/stores/institutions'; import { CollectionsSelectors, GetBookmarksCollectionId } from '../collections/store'; -import { GetUserInstitutions } from '../institutions/store'; import { MyProjectsItem, MyProjectsSearchFilters } from './models'; import { diff --git a/src/app/features/settings/account-settings/store/account-settings.model.ts b/src/app/features/settings/account-settings/store/account-settings.model.ts index 2692056cb..0c91d5e66 100644 --- a/src/app/features/settings/account-settings/store/account-settings.model.ts +++ b/src/app/features/settings/account-settings/store/account-settings.model.ts @@ -1,4 +1,4 @@ -import { Institution } from '@osf/features/institutions/models'; +import { Institution } from '@shared/models'; import { AccountEmail, AccountSettings, ExternalIdentity, Region } from '../models'; diff --git a/src/app/features/settings/account-settings/store/account-settings.selectors.ts b/src/app/features/settings/account-settings/store/account-settings.selectors.ts index 6e5333247..b109dcae5 100644 --- a/src/app/features/settings/account-settings/store/account-settings.selectors.ts +++ b/src/app/features/settings/account-settings/store/account-settings.selectors.ts @@ -1,6 +1,6 @@ import { Selector } from '@ngxs/store'; -import { Institution } from '@osf/features/institutions/models'; +import { Institution } from '@shared/models'; import { AccountEmail, AccountSettings, ExternalIdentity, Region } from '../models'; diff --git a/src/app/features/settings/account-settings/store/account-settings.state.ts b/src/app/features/settings/account-settings/store/account-settings.state.ts index 4e19b011a..852d4e815 100644 --- a/src/app/features/settings/account-settings/store/account-settings.state.ts +++ b/src/app/features/settings/account-settings/store/account-settings.state.ts @@ -5,7 +5,7 @@ import { finalize, tap } from 'rxjs'; import { inject, Injectable } from '@angular/core'; import { SetCurrentUser } from '@core/store/user'; -import { InstitutionsService } from '@osf/features/institutions/services'; +import { InstitutionsService } from '@shared/services'; import { AccountSettingsService } from '../services'; diff --git a/src/app/shared/components/add-project-form/add-project-form.component.spec.ts b/src/app/shared/components/add-project-form/add-project-form.component.spec.ts index 89eb60d67..756445ad3 100644 --- a/src/app/shared/components/add-project-form/add-project-form.component.spec.ts +++ b/src/app/shared/components/add-project-form/add-project-form.component.spec.ts @@ -10,12 +10,13 @@ import { provideHttpClientTesting } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MY_PROJECTS_TABLE_PARAMS } from '@osf/core/constants/my-projects-table.constants'; -import { InstitutionsState } from '@osf/features/institutions/store'; import { CreateProject, GetMyProjects, MyProjectsState } from '@osf/features/my-projects/store'; import { ProjectFormControls } from '@osf/shared/enums/create-project-form-controls.enum'; import { AddProjectFormComponent } from './add-project-form.component'; +import { InstitutionsState } from 'src/app/shared/stores/institutions'; + describe('AddProjectFormComponent', () => { let component: AddProjectFormComponent; let fixture: ComponentFixture; diff --git a/src/app/shared/components/add-project-form/add-project-form.component.ts b/src/app/shared/components/add-project-form/add-project-form.component.ts index 431820943..916c6e738 100644 --- a/src/app/shared/components/add-project-form/add-project-form.component.ts +++ b/src/app/shared/components/add-project-form/add-project-form.component.ts @@ -16,13 +16,14 @@ import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angula import { MY_PROJECTS_TABLE_PARAMS } from '@core/constants/my-projects-table.constants'; import { STORAGE_LOCATIONS } from '@core/constants/storage-locations.constant'; -import { InstitutionsSelectors } from '@osf/features/institutions/store'; import { CreateProject, GetMyProjects, MyProjectsSelectors } from '@osf/features/my-projects/store'; import { ProjectFormControls } from '@osf/shared/enums/create-project-form-controls.enum'; import { ProjectForm } from '@osf/shared/models/create-project-form.model'; import { CustomValidators } from '@osf/shared/utils'; import { IS_XSMALL } from '@shared/utils/breakpoints.tokens'; +import { InstitutionsSelectors } from 'src/app/shared/stores/institutions'; + @Component({ selector: 'osf-add-project-form', imports: [ diff --git a/src/app/shared/components/addons/addon-terms/addon-terms.component.ts b/src/app/shared/components/addons/addon-terms/addon-terms.component.ts index 0d5c616ba..0c394175f 100644 --- a/src/app/shared/components/addons/addon-terms/addon-terms.component.ts +++ b/src/app/shared/components/addons/addon-terms/addon-terms.component.ts @@ -11,7 +11,6 @@ import { isCitationAddon } from '@shared/utils'; @Component({ selector: 'osf-addon-terms', - standalone: true, imports: [TranslatePipe, TableModule, NgClass], templateUrl: './addon-terms.component.html', styleUrls: ['./addon-terms.component.scss'], diff --git a/src/app/shared/components/sub-header/sub-header.component.html b/src/app/shared/components/sub-header/sub-header.component.html index e2cc778eb..78a577e25 100644 --- a/src/app/shared/components/sub-header/sub-header.component.html +++ b/src/app/shared/components/sub-header/sub-header.component.html @@ -30,6 +30,6 @@

@if (description()) { -

{{ description() }}

+

} diff --git a/src/app/shared/components/sub-header/sub-header.component.ts b/src/app/shared/components/sub-header/sub-header.component.ts index 449290e4b..3e2fb91b0 100644 --- a/src/app/shared/components/sub-header/sub-header.component.ts +++ b/src/app/shared/components/sub-header/sub-header.component.ts @@ -1,4 +1,5 @@ import { Button } from 'primeng/button'; +import { SafeHtmlPipe } from 'primeng/menu'; import { Skeleton } from 'primeng/skeleton'; import { Tooltip } from 'primeng/tooltip'; @@ -6,7 +7,7 @@ import { ChangeDetectionStrategy, Component, input, output } from '@angular/core @Component({ selector: 'osf-sub-header', - imports: [Button, Tooltip, Skeleton], + imports: [Button, Tooltip, Skeleton, SafeHtmlPipe], templateUrl: './sub-header.component.html', styleUrl: './sub-header.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/src/app/shared/mappers/index.ts b/src/app/shared/mappers/index.ts index 82df7cbb1..3aa68b980 100644 --- a/src/app/shared/mappers/index.ts +++ b/src/app/shared/mappers/index.ts @@ -1,3 +1,4 @@ export * from './addon.mapper'; export * from './filters'; +export * from './institutions'; export * from './resource-card'; diff --git a/src/app/shared/mappers/institutions/general-institution.mapper.ts b/src/app/shared/mappers/institutions/general-institution.mapper.ts new file mode 100644 index 000000000..15dc684b6 --- /dev/null +++ b/src/app/shared/mappers/institutions/general-institution.mapper.ts @@ -0,0 +1,25 @@ +import { FetchInstitutionsJsonApi, GetGeneralInstitutionsResponse, Institution, InstitutionData } from '@shared/models'; + +export class GeneralInstitutionMapper { + static adaptInstitution(data: InstitutionData): Institution { + return { + id: data.id, + type: data.type, + name: data.attributes.name, + description: data.attributes.description, + iri: data.attributes.iri, + rorIri: data.attributes.ror_iri, + iris: data.attributes.iris, + assets: data.attributes.assets, + institutionalRequestAccessEnabled: data.attributes.institutional_request_access_enabled, + logoPath: data.attributes.logo_path, + }; + } + + static adaptInstitutions(response: FetchInstitutionsJsonApi): GetGeneralInstitutionsResponse { + return { + data: response.data.map((institution) => this.adaptInstitution(institution)), + total: response.links.meta.total, + }; + } +} diff --git a/src/app/shared/mappers/institutions/index.ts b/src/app/shared/mappers/institutions/index.ts new file mode 100644 index 000000000..7931a9fad --- /dev/null +++ b/src/app/shared/mappers/institutions/index.ts @@ -0,0 +1,2 @@ +export * from './general-institution.mapper'; +export * from './user-institutions.mapper'; diff --git a/src/app/features/institutions/mappers/institutions.mapper.ts b/src/app/shared/mappers/institutions/user-institutions.mapper.ts similarity index 83% rename from src/app/features/institutions/mappers/institutions.mapper.ts rename to src/app/shared/mappers/institutions/user-institutions.mapper.ts index 4b7751846..c9d12cb8f 100644 --- a/src/app/features/institutions/mappers/institutions.mapper.ts +++ b/src/app/shared/mappers/institutions/user-institutions.mapper.ts @@ -1,6 +1,6 @@ -import { Institution, UserInstitutionGetResponse } from '../models'; +import { Institution, UserInstitutionGetResponse } from '@shared/models'; -export class InstitutionsMapper { +export class UserInstitutionsMapper { static fromResponse(response: UserInstitutionGetResponse): Institution { return { id: response.id, diff --git a/src/app/shared/models/index.ts b/src/app/shared/models/index.ts index cd39f2cd2..77ec9cce0 100644 --- a/src/app/shared/models/index.ts +++ b/src/app/shared/models/index.ts @@ -6,6 +6,7 @@ export * from './create-project-form.model'; export * from './filter-labels.model'; export * from './filters'; export * from './google-drive-folder.model'; +export * from './institutions'; export * from './metadata-field.model'; export * from './nav-item.model'; export * from './node-response.model'; diff --git a/src/app/shared/models/institutions/index.ts b/src/app/shared/models/institutions/index.ts new file mode 100644 index 000000000..64a2ea307 --- /dev/null +++ b/src/app/shared/models/institutions/index.ts @@ -0,0 +1,2 @@ +export * from './institutions.models'; +export * from './institutions-json-api.model'; diff --git a/src/app/shared/models/institutions/institutions-json-api.model.ts b/src/app/shared/models/institutions/institutions-json-api.model.ts new file mode 100644 index 000000000..49017a50c --- /dev/null +++ b/src/app/shared/models/institutions/institutions-json-api.model.ts @@ -0,0 +1,66 @@ +import { Institution, InstitutionAttributes } from '@shared/models'; + +export interface InstitutionRelationships { + nodes: { + links: { + related: { + href: string; + meta: Record; + }; + }; + }; + registrations: { + links: { + related: { + href: string; + meta: Record; + }; + }; + }; + users: { + links: { + related: { + href: string; + meta: Record; + }; + }; + }; +} + +export interface InstitutionLinks { + self: string; + html: string; + iri: string; +} + +export interface InstitutionData { + id: string; + type: string; + attributes: InstitutionAttributes; + relationships: InstitutionRelationships; + links: InstitutionLinks; +} + +export interface InstitutionsResponseLinks { + first: string | null; + last: string | null; + prev: string | null; + next: string | null; + meta: { + total: number; + per_page: number; + }; +} + +export interface FetchInstitutionsJsonApi { + data: InstitutionData[]; + links: InstitutionsResponseLinks; + meta: { + version: string; + }; +} + +export interface GetGeneralInstitutionsResponse { + data: Institution[]; + total: number; +} diff --git a/src/app/features/institutions/models/institutions.models.ts b/src/app/shared/models/institutions/institutions.models.ts similarity index 100% rename from src/app/features/institutions/models/institutions.models.ts rename to src/app/shared/models/institutions/institutions.models.ts diff --git a/src/app/shared/services/index.ts b/src/app/shared/services/index.ts index da88d19c0..0bffeb80c 100644 --- a/src/app/shared/services/index.ts +++ b/src/app/shared/services/index.ts @@ -1,6 +1,7 @@ export * from './addons'; export { CustomConfirmationService } from './custom-confirmation.service'; export { FiltersOptionsService } from './filters-options.service'; +export { InstitutionsService } from './institutions.service'; export { LoaderService } from './loader.service'; export { ResourceCardService } from './resource-card.service'; export { SearchService } from './search.service'; diff --git a/src/app/shared/services/institutions.service.ts b/src/app/shared/services/institutions.service.ts new file mode 100644 index 000000000..d055c06ad --- /dev/null +++ b/src/app/shared/services/institutions.service.ts @@ -0,0 +1,62 @@ +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { inject, Injectable } from '@angular/core'; + +import { JsonApiResponse } from '@core/models'; +import { JsonApiService } from '@core/services'; +import { GeneralInstitutionMapper, UserInstitutionsMapper } from '@shared/mappers'; +import { + FetchInstitutionsJsonApi, + GetGeneralInstitutionsResponse, + Institution, + UserInstitutionGetResponse, +} from '@shared/models'; + +import { environment } from '../../../environments/environment'; + +@Injectable({ + providedIn: 'root', +}) +export class InstitutionsService { + private readonly jsonApiService = inject(JsonApiService); + + getInstitutions( + pageNumber: number, + pageSize: number, + searchValue?: string + ): Observable { + const params: Record = {}; + + if (pageNumber) { + params['page'] = pageNumber; + } + + if (pageSize) { + params['page[size]'] = pageSize; + } + + if (searchValue && searchValue.trim()) { + params['filter[name]'] = searchValue.trim(); + } + + return this.jsonApiService + .get(`${environment.apiUrl}/institutions`, params) + .pipe(map((response) => GeneralInstitutionMapper.adaptInstitutions(response))); + } + + getUserInstitutions(): Observable { + const url = `${environment.apiUrl}/users/me/institutions/`; + + return this.jsonApiService + .get>(url) + .pipe(map((response) => response.data.map((item) => UserInstitutionsMapper.fromResponse(item)))); + } + + deleteUserInstitution(id: string, userId: string): Observable { + const payload = { + data: [{ id: id, type: 'institutions' }], + }; + return this.jsonApiService.delete(`${environment.apiUrl}/users/${userId}/relationships/institutions/`, payload); + } +} diff --git a/src/app/shared/stores/index.ts b/src/app/shared/stores/index.ts new file mode 100644 index 000000000..b94bb4458 --- /dev/null +++ b/src/app/shared/stores/index.ts @@ -0,0 +1,2 @@ +export * from './addons'; +export * from './institutions'; diff --git a/src/app/features/institutions/store/index.ts b/src/app/shared/stores/institutions/index.ts similarity index 100% rename from src/app/features/institutions/store/index.ts rename to src/app/shared/stores/institutions/index.ts diff --git a/src/app/shared/stores/institutions/institutions.actions.ts b/src/app/shared/stores/institutions/institutions.actions.ts new file mode 100644 index 000000000..cd554fcbe --- /dev/null +++ b/src/app/shared/stores/institutions/institutions.actions.ts @@ -0,0 +1,13 @@ +export class GetUserInstitutions { + static readonly type = '[Institutions] Get User Institutions'; +} + +export class GetInstitutions { + static readonly type = '[Institutions] Get'; + + constructor( + public pageNumber: number, + public pageSize: number, + public searchValue?: string + ) {} +} diff --git a/src/app/shared/stores/institutions/institutions.model.ts b/src/app/shared/stores/institutions/institutions.model.ts new file mode 100644 index 000000000..aaa403afb --- /dev/null +++ b/src/app/shared/stores/institutions/institutions.model.ts @@ -0,0 +1,7 @@ +import { Institution } from '@shared/models'; +import { AsyncStateWithTotalCount } from '@shared/models/store/async-state-with-total-count.model'; + +export interface InstitutionsStateModel { + userInstitutions: Institution[]; + institutions: AsyncStateWithTotalCount; +} diff --git a/src/app/shared/stores/institutions/institutions.selectors.ts b/src/app/shared/stores/institutions/institutions.selectors.ts new file mode 100644 index 000000000..9414d163d --- /dev/null +++ b/src/app/shared/stores/institutions/institutions.selectors.ts @@ -0,0 +1,26 @@ +import { Selector } from '@ngxs/store'; + +import { InstitutionsStateModel } from './institutions.model'; +import { InstitutionsState } from './institutions.state'; + +export class InstitutionsSelectors { + @Selector([InstitutionsState]) + static getUserInstitutions(state: InstitutionsStateModel) { + return state.userInstitutions; + } + + @Selector([InstitutionsState]) + static getInstitutions(state: InstitutionsStateModel) { + return state.institutions.data; + } + + @Selector([InstitutionsState]) + static isInstitutionsLoading(state: InstitutionsStateModel): boolean { + return state.institutions.isLoading; + } + + @Selector([InstitutionsState]) + static getInstitutionsTotalCount(state: InstitutionsStateModel): number { + return state.institutions.totalCount; + } +} diff --git a/src/app/shared/stores/institutions/institutions.state.ts b/src/app/shared/stores/institutions/institutions.state.ts new file mode 100644 index 000000000..ee0ae8129 --- /dev/null +++ b/src/app/shared/stores/institutions/institutions.state.ts @@ -0,0 +1,57 @@ +import { Action, State, StateContext } from '@ngxs/store'; +import { patch } from '@ngxs/store/operators'; + +import { tap } from 'rxjs'; + +import { inject, Injectable } from '@angular/core'; + +import { InstitutionsService } from '@shared/services'; +import { GetInstitutions, GetUserInstitutions, InstitutionsStateModel } from '@shared/stores'; + +@State({ + name: 'institutions', + defaults: { + userInstitutions: [], + institutions: { + data: [], + isLoading: false, + error: null, + isSubmitting: false, + totalCount: 0, + }, + }, +}) +@Injectable() +export class InstitutionsState { + private readonly institutionsService = inject(InstitutionsService); + + @Action(GetUserInstitutions) + getUserInstitutions(ctx: StateContext) { + return this.institutionsService.getUserInstitutions().pipe( + tap((institutions) => { + ctx.patchState({ + userInstitutions: institutions, + }); + }) + ); + } + + @Action(GetInstitutions) + getInstitutions(ctx: StateContext, action: GetInstitutions) { + return this.institutionsService.getInstitutions(action.pageNumber, action.pageSize, action.searchValue).pipe( + tap((response) => { + ctx.setState( + patch({ + institutions: patch({ + data: response.data, + totalCount: response.total, + isSubmitting: false, + error: null, + isLoading: false, + }), + }) + ); + }) + ); + } +} diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index c693d5445..e276e790a 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -77,7 +77,8 @@ "contributors": "Contributors", "analytics": "Analytics", "addons": "Add-ons" - } + }, + "institutions": "Institutions" }, "auth": { "common": { @@ -1399,5 +1400,11 @@ "description": "Remember to add metadata and resources to your own work on OSF to make it more discoverable! Learn more in our help guides." }, "stepCount": "{{current}} of {{total}}" + }, + "institutions": { + "title": "Institutions", + "description": "OSF Institutions enhances transparency and increases the visibility of research outputs, accelerating discovery and reuse. Institutional members focus on generating and sharing research, and let OSF Institutions handle the infrastructure.
Read more", + "searchInstitutions": "Search institutions" + } } From f422ead64f3124302ff0572b801762be9c8be3d6 Mon Sep 17 00:00:00 2001 From: volodyayakubovskyy Date: Mon, 23 Jun 2025 15:50:48 +0300 Subject: [PATCH 2/3] feat(institutions): added fixes by suggestions --- .../institutions/institutions.component.html | 38 +++++++++++-------- .../institutions/institutions.component.ts | 22 ++++++++--- src/app/features/meetings/constants/index.ts | 1 - .../meetings-landing.component.ts | 2 +- src/app/shared/constants/index.ts | 1 + .../constants/meetings-table.constants.ts | 2 +- .../shared/services/institutions.service.ts | 2 +- .../institutions/institutions.actions.ts | 2 +- .../stores/institutions/institutions.state.ts | 28 +++++++++++--- src/assets/i18n/en.json | 1 - 10 files changed, 66 insertions(+), 33 deletions(-) rename src/app/{features/meetings => shared}/constants/meetings-table.constants.ts (81%) diff --git a/src/app/features/institutions/institutions.component.html b/src/app/features/institutions/institutions.component.html index 64ea61f55..505a518b9 100644 --- a/src/app/features/institutions/institutions.component.html +++ b/src/app/features/institutions/institutions.component.html @@ -6,23 +6,29 @@ [icon]="'institutions'" /> -
- + @if (institutionsLoading()) { +
+ +
+ } @else { +
+ -
- @for (institution of institutions(); track $index) { -
- +
+ @for (institution of institutions(); track $index) { +
+ -

{{ institution.name }}

-
- } -
+

{{ institution.name }}

+
+ } +
- -
+ +
+ } diff --git a/src/app/features/institutions/institutions.component.ts b/src/app/features/institutions/institutions.component.ts index 8d525cdeb..d47dfde4e 100644 --- a/src/app/features/institutions/institutions.component.ts +++ b/src/app/features/institutions/institutions.component.ts @@ -13,14 +13,26 @@ import { FormControl } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { parseQueryFilterParams } from '@core/helpers'; -import { MEETINGS_TABLE_PARAMS } from '@osf/features/meetings/constants'; -import { CustomPaginatorComponent, SearchInputComponent, SubHeaderComponent } from '@shared/components'; +import { + CustomPaginatorComponent, + LoadingSpinnerComponent, + SearchInputComponent, + SubHeaderComponent, +} from '@shared/components'; +import { MEETINGS_TABLE_PARAMS } from '@shared/constants'; import { QueryParams } from '@shared/models'; -import { GetInstitutions, InstitutionsSelectors } from '@shared/stores'; +import { FetchInstitutions, InstitutionsSelectors } from '@shared/stores'; @Component({ selector: 'osf-institutions', - imports: [SubHeaderComponent, TranslatePipe, SearchInputComponent, NgOptimizedImage, CustomPaginatorComponent], + imports: [ + SubHeaderComponent, + TranslatePipe, + SearchInputComponent, + NgOptimizedImage, + CustomPaginatorComponent, + LoadingSpinnerComponent, + ], templateUrl: './institutions.component.html', styleUrl: './institutions.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, @@ -30,7 +42,7 @@ export class InstitutionsComponent { private readonly router = inject(Router); private readonly destroyRef = inject(DestroyRef); - private readonly actions = createDispatchMap({ getInstitutions: GetInstitutions }); + private readonly actions = createDispatchMap({ getInstitutions: FetchInstitutions }); searchControl = new FormControl(''); diff --git a/src/app/features/meetings/constants/index.ts b/src/app/features/meetings/constants/index.ts index 8042264ba..361431603 100644 --- a/src/app/features/meetings/constants/index.ts +++ b/src/app/features/meetings/constants/index.ts @@ -1,3 +1,2 @@ export * from './meeting-submissions-table.constants'; export * from './meetings-fields.constants'; -export * from './meetings-table.constants'; diff --git a/src/app/features/meetings/pages/meetings-landing/meetings-landing.component.ts b/src/app/features/meetings/pages/meetings-landing/meetings-landing.component.ts index 998d8d6dd..b59bc4e7c 100644 --- a/src/app/features/meetings/pages/meetings-landing/meetings-landing.component.ts +++ b/src/app/features/meetings/pages/meetings-landing/meetings-landing.component.ts @@ -25,11 +25,11 @@ import { FormControl } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { parseQueryFilterParams } from '@core/helpers'; -import { MEETINGS_TABLE_PARAMS } from '@osf/features/meetings/constants'; import { Meeting } from '@osf/features/meetings/models'; import { GetAllMeetings, MeetingsSelectors } from '@osf/features/meetings/store'; import { IS_XSMALL } from '@osf/shared/utils'; import { SearchInputComponent, SubHeaderComponent } from '@shared/components'; +import { MEETINGS_TABLE_PARAMS } from '@shared/constants'; import { SortOrder } from '@shared/enums'; import { QueryParams, TableParameters } from '@shared/models'; import { SearchFilters } from '@shared/models/filters'; diff --git a/src/app/shared/constants/index.ts b/src/app/shared/constants/index.ts index 503a11b31..fd9e0a180 100644 --- a/src/app/shared/constants/index.ts +++ b/src/app/shared/constants/index.ts @@ -3,6 +3,7 @@ export * from './addons-category-options.const'; export * from './addons-tab-options.const'; export * from './input-limits.const'; export * from './input-validation-messages.const'; +export * from './meetings-table.constants'; export * from './remove-nullable.const'; export * from './resource-filters-defaults'; export * from './resource-languages.const'; diff --git a/src/app/features/meetings/constants/meetings-table.constants.ts b/src/app/shared/constants/meetings-table.constants.ts similarity index 81% rename from src/app/features/meetings/constants/meetings-table.constants.ts rename to src/app/shared/constants/meetings-table.constants.ts index 962cb9372..0e515bcd4 100644 --- a/src/app/features/meetings/constants/meetings-table.constants.ts +++ b/src/app/shared/constants/meetings-table.constants.ts @@ -1,4 +1,4 @@ -import { TableParameters } from '@osf/shared/models'; +import { TableParameters } from '@shared/models'; export const MEETINGS_TABLE_PARAMS: TableParameters = { rows: 10, diff --git a/src/app/shared/services/institutions.service.ts b/src/app/shared/services/institutions.service.ts index d055c06ad..3331b95aa 100644 --- a/src/app/shared/services/institutions.service.ts +++ b/src/app/shared/services/institutions.service.ts @@ -13,7 +13,7 @@ import { UserInstitutionGetResponse, } from '@shared/models'; -import { environment } from '../../../environments/environment'; +import { environment } from 'src/environments/environment'; @Injectable({ providedIn: 'root', diff --git a/src/app/shared/stores/institutions/institutions.actions.ts b/src/app/shared/stores/institutions/institutions.actions.ts index cd554fcbe..8bcc866b0 100644 --- a/src/app/shared/stores/institutions/institutions.actions.ts +++ b/src/app/shared/stores/institutions/institutions.actions.ts @@ -2,7 +2,7 @@ export class GetUserInstitutions { static readonly type = '[Institutions] Get User Institutions'; } -export class GetInstitutions { +export class FetchInstitutions { static readonly type = '[Institutions] Get'; constructor( diff --git a/src/app/shared/stores/institutions/institutions.state.ts b/src/app/shared/stores/institutions/institutions.state.ts index ee0ae8129..45a2a9332 100644 --- a/src/app/shared/stores/institutions/institutions.state.ts +++ b/src/app/shared/stores/institutions/institutions.state.ts @@ -1,12 +1,12 @@ import { Action, State, StateContext } from '@ngxs/store'; import { patch } from '@ngxs/store/operators'; -import { tap } from 'rxjs'; +import { catchError, tap, throwError } from 'rxjs'; import { inject, Injectable } from '@angular/core'; import { InstitutionsService } from '@shared/services'; -import { GetInstitutions, GetUserInstitutions, InstitutionsStateModel } from '@shared/stores'; +import { FetchInstitutions, GetUserInstitutions, InstitutionsStateModel } from '@shared/stores'; @State({ name: 'institutions', @@ -16,7 +16,6 @@ import { GetInstitutions, GetUserInstitutions, InstitutionsStateModel } from '@s data: [], isLoading: false, error: null, - isSubmitting: false, totalCount: 0, }, }, @@ -36,8 +35,16 @@ export class InstitutionsState { ); } - @Action(GetInstitutions) - getInstitutions(ctx: StateContext, action: GetInstitutions) { + @Action(FetchInstitutions) + getInstitutions(ctx: StateContext, action: FetchInstitutions) { + ctx.patchState({ + institutions: { + ...ctx.getState().institutions, + isLoading: true, + error: null, + }, + }); + return this.institutionsService.getInstitutions(action.pageNumber, action.pageSize, action.searchValue).pipe( tap((response) => { ctx.setState( @@ -45,12 +52,21 @@ export class InstitutionsState { institutions: patch({ data: response.data, totalCount: response.total, - isSubmitting: false, error: null, isLoading: false, }), }) ); + }), + catchError((error) => { + ctx.patchState({ + institutions: { + ...ctx.getState().institutions, + isLoading: false, + error, + }, + }); + return throwError(() => error); }) ); } diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 5a8680fa6..3872a06a8 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -1456,6 +1456,5 @@ "title": "Institutions", "description": "OSF Institutions enhances transparency and increases the visibility of research outputs, accelerating discovery and reuse. Institutional members focus on generating and sharing research, and let OSF Institutions handle the infrastructure.
Read more", "searchInstitutions": "Search institutions" - } } From 90ad94203979dbf403d30bc66a60ba9efdea8d84 Mon Sep 17 00:00:00 2001 From: volodyayakubovskyy Date: Mon, 23 Jun 2025 16:10:17 +0300 Subject: [PATCH 3/3] feat(institutions): added fixes by suggestions --- src/app/features/institutions/institutions.component.ts | 4 ++-- .../pages/meetings-landing/meetings-landing.component.ts | 6 +++--- src/app/shared/constants/meetings-table.constants.ts | 2 +- src/app/shared/stores/institutions/institutions.state.ts | 3 ++- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/app/features/institutions/institutions.component.ts b/src/app/features/institutions/institutions.component.ts index d47dfde4e..2b5557c25 100644 --- a/src/app/features/institutions/institutions.component.ts +++ b/src/app/features/institutions/institutions.component.ts @@ -19,7 +19,7 @@ import { SearchInputComponent, SubHeaderComponent, } from '@shared/components'; -import { MEETINGS_TABLE_PARAMS } from '@shared/constants'; +import { TABLE_PARAMS } from '@shared/constants'; import { QueryParams } from '@shared/models'; import { FetchInstitutions, InstitutionsSelectors } from '@shared/stores'; @@ -48,7 +48,7 @@ export class InstitutionsComponent { queryParams = toSignal(this.route.queryParams); currentPage = signal(1); - currentPageSize = signal(MEETINGS_TABLE_PARAMS.rows); + currentPageSize = signal(TABLE_PARAMS.rows); first = signal(0); institutions = select(InstitutionsSelectors.getInstitutions); diff --git a/src/app/features/meetings/pages/meetings-landing/meetings-landing.component.ts b/src/app/features/meetings/pages/meetings-landing/meetings-landing.component.ts index b59bc4e7c..2740a9cb7 100644 --- a/src/app/features/meetings/pages/meetings-landing/meetings-landing.component.ts +++ b/src/app/features/meetings/pages/meetings-landing/meetings-landing.component.ts @@ -29,7 +29,7 @@ import { Meeting } from '@osf/features/meetings/models'; import { GetAllMeetings, MeetingsSelectors } from '@osf/features/meetings/store'; import { IS_XSMALL } from '@osf/shared/utils'; import { SearchInputComponent, SubHeaderComponent } from '@shared/components'; -import { MEETINGS_TABLE_PARAMS } from '@shared/constants'; +import { TABLE_PARAMS } from '@shared/constants'; import { SortOrder } from '@shared/enums'; import { QueryParams, TableParameters } from '@shared/models'; import { SearchFilters } from '@shared/models/filters'; @@ -55,9 +55,9 @@ export class MeetingsLandingComponent { sortColumn = signal(''); sortOrder = signal(SortOrder.Asc); currentPage = signal(1); - currentPageSize = signal(MEETINGS_TABLE_PARAMS.rows); + currentPageSize = signal(TABLE_PARAMS.rows); tableParams = signal({ - ...MEETINGS_TABLE_PARAMS, + ...TABLE_PARAMS, firstRowIndex: 0, }); diff --git a/src/app/shared/constants/meetings-table.constants.ts b/src/app/shared/constants/meetings-table.constants.ts index 0e515bcd4..769587a07 100644 --- a/src/app/shared/constants/meetings-table.constants.ts +++ b/src/app/shared/constants/meetings-table.constants.ts @@ -1,6 +1,6 @@ import { TableParameters } from '@shared/models'; -export const MEETINGS_TABLE_PARAMS: TableParameters = { +export const TABLE_PARAMS: TableParameters = { rows: 10, paginator: true, scrollable: false, diff --git a/src/app/shared/stores/institutions/institutions.state.ts b/src/app/shared/stores/institutions/institutions.state.ts index 45a2a9332..08b8c86ff 100644 --- a/src/app/shared/stores/institutions/institutions.state.ts +++ b/src/app/shared/stores/institutions/institutions.state.ts @@ -39,7 +39,8 @@ export class InstitutionsState { getInstitutions(ctx: StateContext, action: FetchInstitutions) { ctx.patchState({ institutions: { - ...ctx.getState().institutions, + data: [], + totalCount: 0, isLoading: true, error: null, },