From f52c45612f902cac98905fdb140eef227662156f Mon Sep 17 00:00:00 2001 From: Roman Nastyuk Date: Wed, 23 Jul 2025 17:15:01 +0300 Subject: [PATCH 1/7] feat(collection-moderation): added collection-moderation store --- .../collections/collections.routes.ts | 2 +- .../add-to-collection.component.ts | 4 +- .../collection-metadata-step.component.ts | 2 +- .../select-project-step.component.ts | 2 +- .../collections-discover.component.ts | 6 +- .../collections-filter-chips.component.ts | 2 +- .../collections-filters.component.ts | 2 +- .../collections-main-content.component.ts | 4 +- ...ollections-search-result-card.component.ts | 2 +- .../collections-search-results.component.ts | 4 +- src/app/features/collections/models/index.ts | 4 -- .../services/add-to-collection.service.ts | 6 +- .../collections-query-sync.service.ts | 12 ++-- .../features/collections/services/index.ts | 1 - .../add-to-collection.actions.ts | 2 +- .../collection-moderation.routes.ts | 5 +- .../add-moderator-dialog.component.ts | 2 +- ...tion-moderation-submissions.component.html | 4 +- ...ection-moderation-submissions.component.ts | 68 +++++++++++++++++-- .../moderators-list.component.ts | 2 +- .../mappers/collection-moderation.mapper.ts | 15 ++++ src/app/features/moderation/mappers/index.ts | 1 + ...collection-review-action-json-api.model.ts | 32 +++++++++ .../models/collection-review-action.model.ts | 9 +++ src/app/features/moderation/models/index.ts | 2 + .../moderation/preprint-moderation.routes.ts | 2 +- .../moderation/registry-moderation.routes.ts | 2 +- .../collections-moderation.actions.ts | 10 +++ .../collections-moderation.model.ts | 21 ++++++ .../collections-moderation.selectors.ts | 11 +++ .../collections-moderation.state.ts | 47 +++++++++++++ .../store/collections-moderation/index.ts | 4 ++ .../store/{moderation => moderators}/index.ts | 0 .../moderators.actions.ts | 0 .../moderators.model.ts | 0 .../moderators.selectors.ts | 0 .../moderators.state.ts | 0 .../services/my-projects.service.ts | 16 ----- .../preprints-resources.component.ts | 2 +- .../overview/project-overview.component.ts | 2 +- .../collections}/collections.mapper.ts | 4 +- .../mappers/collections}/index.ts | 0 src/app/shared/mappers/index.ts | 1 + ...ction-submission-payload-json-api.model.ts | 0 .../collection-submission-payload.model.ts} | 0 .../collections}/collections-filters.model.ts | 0 .../collections-json-api.models.ts | 2 +- .../models/collections}/collections.models.ts | 0 src/app/shared/models/collections/index.ts | 5 ++ src/app/shared/models/index.ts | 1 + src/app/shared/services/bookmarks.service.ts | 2 +- .../services/collections.service.ts | 37 ++++++++-- src/app/shared/services/index.ts | 1 + .../collections/collections.actions.ts | 2 +- .../stores}/collections/collections.model.ts | 7 +- .../collections/collections.selectors.ts | 4 +- .../stores}/collections/collections.state.ts | 2 +- .../stores}/collections/index.ts | 0 src/app/shared/stores/index.ts | 1 + 59 files changed, 295 insertions(+), 86 deletions(-) create mode 100644 src/app/features/moderation/mappers/collection-moderation.mapper.ts create mode 100644 src/app/features/moderation/models/collection-review-action-json-api.model.ts create mode 100644 src/app/features/moderation/models/collection-review-action.model.ts create mode 100644 src/app/features/moderation/store/collections-moderation/collections-moderation.actions.ts create mode 100644 src/app/features/moderation/store/collections-moderation/collections-moderation.model.ts create mode 100644 src/app/features/moderation/store/collections-moderation/collections-moderation.selectors.ts create mode 100644 src/app/features/moderation/store/collections-moderation/collections-moderation.state.ts create mode 100644 src/app/features/moderation/store/collections-moderation/index.ts rename src/app/features/moderation/store/{moderation => moderators}/index.ts (100%) rename src/app/features/moderation/store/{moderation => moderators}/moderators.actions.ts (100%) rename src/app/features/moderation/store/{moderation => moderators}/moderators.model.ts (100%) rename src/app/features/moderation/store/{moderation => moderators}/moderators.selectors.ts (100%) rename src/app/features/moderation/store/{moderation => moderators}/moderators.state.ts (100%) rename src/app/{features/collections/mappers => shared/mappers/collections}/collections.mapper.ts (97%) rename src/app/{features/collections/mappers => shared/mappers/collections}/index.ts (100%) rename src/app/{features/collections/models => shared/models/collections}/collection-submission-payload-json-api.model.ts (100%) rename src/app/{features/collections/models/collection-submission-payload.ts => shared/models/collections/collection-submission-payload.model.ts} (100%) rename src/app/{features/collections/models => shared/models/collections}/collections-filters.model.ts (100%) rename src/app/{features/collections/models => shared/models/collections}/collections-json-api.models.ts (98%) rename src/app/{features/collections/models => shared/models/collections}/collections.models.ts (100%) create mode 100644 src/app/shared/models/collections/index.ts rename src/app/{features/collections => shared}/services/collections.service.ts (82%) rename src/app/{features/collections/store => shared/stores}/collections/collections.actions.ts (97%) rename src/app/{features/collections/store => shared/stores}/collections/collections.model.ts (78%) rename src/app/{features/collections/store => shared/stores}/collections/collections.selectors.ts (93%) rename src/app/{features/collections/store => shared/stores}/collections/collections.state.ts (99%) rename src/app/{features/collections/store => shared/stores}/collections/index.ts (100%) diff --git a/src/app/features/collections/collections.routes.ts b/src/app/features/collections/collections.routes.ts index 3e5412ab7..41f8178c8 100644 --- a/src/app/features/collections/collections.routes.ts +++ b/src/app/features/collections/collections.routes.ts @@ -3,9 +3,9 @@ import { provideStates } from '@ngxs/store'; import { Routes } from '@angular/router'; import { AddToCollectionState } from '@osf/features/collections/store/add-to-collection'; -import { CollectionsState } from '@osf/features/collections/store/collections'; import { ConfirmLeavingGuard } from '@shared/guards'; import { ContributorsState, ProjectsState } from '@shared/stores'; +import { CollectionsState } from '@shared/stores/collections'; export const collectionsRoutes: Routes = [ { diff --git a/src/app/features/collections/components/add-to-collection/add-to-collection.component.ts b/src/app/features/collections/components/add-to-collection/add-to-collection.component.ts index d771da099..5651f71fd 100644 --- a/src/app/features/collections/components/add-to-collection/add-to-collection.component.ts +++ b/src/app/features/collections/components/add-to-collection/add-to-collection.component.ts @@ -26,10 +26,10 @@ import { AddToCollectionSteps } from '@osf/features/collections/enums'; import { ClearAddToCollectionState, CreateCollectionSubmission, -} from '@osf/features/collections/store/add-to-collection/add-to-collection.actions'; -import { CollectionsSelectors, GetCollectionProvider } from '@osf/features/collections/store/collections'; +} from '@osf/features/collections/store/add-to-collection'; import { LoadingSpinnerComponent } from '@shared/components'; import { CanDeactivateComponent } from '@shared/models'; +import { CollectionsSelectors, GetCollectionProvider } from '@shared/stores'; import { ProjectsSelectors } from '@shared/stores/projects/projects.selectors'; import { diff --git a/src/app/features/collections/components/add-to-collection/collection-metadata-step/collection-metadata-step.component.ts b/src/app/features/collections/components/add-to-collection/collection-metadata-step/collection-metadata-step.component.ts index 2816d42f9..86e974b06 100644 --- a/src/app/features/collections/components/add-to-collection/collection-metadata-step/collection-metadata-step.component.ts +++ b/src/app/features/collections/components/add-to-collection/collection-metadata-step/collection-metadata-step.component.ts @@ -13,7 +13,7 @@ import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angula import { collectionFilterTypes } from '@osf/features/collections/constants/filter-types.const'; import { AddToCollectionSteps } from '@osf/features/collections/enums'; import { CollectionFilterEntry } from '@osf/features/collections/models'; -import { CollectionsSelectors, GetCollectionDetails } from '@osf/features/collections/store/collections'; +import { CollectionsSelectors, GetCollectionDetails } from '@shared/stores'; @Component({ selector: 'osf-collection-metadata-step', diff --git a/src/app/features/collections/components/add-to-collection/select-project-step/select-project-step.component.ts b/src/app/features/collections/components/add-to-collection/select-project-step/select-project-step.component.ts index 300db8df2..8cb47647b 100644 --- a/src/app/features/collections/components/add-to-collection/select-project-step/select-project-step.component.ts +++ b/src/app/features/collections/components/add-to-collection/select-project-step/select-project-step.component.ts @@ -23,10 +23,10 @@ import { FormsModule } from '@angular/forms'; import { UserSelectors } from '@core/store/user'; import { AddToCollectionSteps } from '@osf/features/collections/enums'; -import { CollectionsSelectors, GetUserCollectionSubmissions } from '@osf/features/collections/store/collections'; import { GetProjects, SetSelectedProject } from '@osf/shared/stores'; import { CustomOption } from '@shared/models'; import { Project } from '@shared/models/projects'; +import { CollectionsSelectors, GetUserCollectionSubmissions } from '@shared/stores/collections'; import { ProjectsSelectors } from '@shared/stores/projects/projects.selectors'; @Component({ diff --git a/src/app/features/collections/components/collections-discover/collections-discover.component.ts b/src/app/features/collections/components/collections-discover/collections-discover.component.ts index 14fa8bcb0..2a4fcff3f 100644 --- a/src/app/features/collections/components/collections-discover/collections-discover.component.ts +++ b/src/app/features/collections/components/collections-discover/collections-discover.component.ts @@ -13,8 +13,9 @@ import { FormControl } from '@angular/forms'; import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { CollectionsHelpDialogComponent, CollectionsMainContentComponent } from '@osf/features/collections/components'; -import { CollectionsFilters } from '@osf/features/collections/models'; import { CollectionsQuerySyncService } from '@osf/features/collections/services'; +import { LoadingSpinnerComponent, SearchInputComponent } from '@shared/components'; +import { CollectionsFilters } from '@shared/models'; import { ClearCollections, ClearCollectionSubmissions, @@ -24,8 +25,7 @@ import { SearchCollectionSubmissions, SetPageNumber, SetSearchValue, -} from '@osf/features/collections/store/collections'; -import { LoadingSpinnerComponent, SearchInputComponent } from '@shared/components'; +} from '@shared/stores/collections'; @Component({ selector: 'osf-collections-discover', diff --git a/src/app/features/collections/components/collections-filter-chips/collections-filter-chips.component.ts b/src/app/features/collections/components/collections-filter-chips/collections-filter-chips.component.ts index 392395f71..7523ddba6 100644 --- a/src/app/features/collections/components/collections-filter-chips/collections-filter-chips.component.ts +++ b/src/app/features/collections/components/collections-filter-chips/collections-filter-chips.component.ts @@ -18,7 +18,7 @@ import { SetStatusFilters, SetStudyDesignFilters, SetVolumeFilters, -} from '@osf/features/collections/store/collections'; +} from '@shared/stores/collections'; @Component({ selector: 'osf-collections-filter-chips', diff --git a/src/app/features/collections/components/collections-filters/collections-filters.component.ts b/src/app/features/collections/components/collections-filters/collections-filters.component.ts index ab00fba8d..cae7d8b17 100644 --- a/src/app/features/collections/components/collections-filters/collections-filters.component.ts +++ b/src/app/features/collections/components/collections-filters/collections-filters.component.ts @@ -22,7 +22,7 @@ import { SetStatusFilters, SetStudyDesignFilters, SetVolumeFilters, -} from '@osf/features/collections/store/collections'; +} from '@shared/stores/collections'; @Component({ selector: 'osf-collections-filters', diff --git a/src/app/features/collections/components/collections-main-content/collections-main-content.component.ts b/src/app/features/collections/components/collections-main-content/collections-main-content.component.ts index acd1c9976..bfcbfd1ff 100644 --- a/src/app/features/collections/components/collections-main-content/collections-main-content.component.ts +++ b/src/app/features/collections/components/collections-main-content/collections-main-content.component.ts @@ -12,7 +12,7 @@ import { FormsModule } from '@angular/forms'; import { CollectionsFilterChipsComponent } from '@osf/features/collections/components'; import { collectionsSortOptions } from '@osf/features/collections/constants'; -import { CollectionsSelectors, SetSortBy } from '@osf/features/collections/store/collections'; +import { CollectionsSelectors, SetSortBy } from '@shared/stores/collections'; import { IS_WEB } from '@shared/utils'; import { CollectionsFiltersComponent } from '../collections-filters/collections-filters.component'; @@ -38,7 +38,7 @@ export class CollectionsMainContentComponent { protected readonly sortOptions = collectionsSortOptions; protected isWeb = toSignal(inject(IS_WEB)); protected selectedSort = select(CollectionsSelectors.getSortBy); - protected collectionSubmissions = select(CollectionsSelectors.getCollectionSubmissions); + protected collectionSubmissions = select(CollectionsSelectors.getCollectionSubmissionsSearchResult); protected isCollectionSubmissionsLoading = select(CollectionsSelectors.getCollectionSubmissionsLoading); protected isFiltersOpen = signal(false); diff --git a/src/app/features/collections/components/collections-search-result-card/collections-search-result-card.component.ts b/src/app/features/collections/components/collections-search-result-card/collections-search-result-card.component.ts index f0427835f..2f5dcb38d 100644 --- a/src/app/features/collections/components/collections-search-result-card/collections-search-result-card.component.ts +++ b/src/app/features/collections/components/collections-search-result-card/collections-search-result-card.component.ts @@ -4,7 +4,7 @@ import { DatePipe } from '@angular/common'; import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core'; import { collectionFilterNames } from '@osf/features/collections/constants'; -import { CollectionSubmission } from '@osf/features/collections/models'; +import { CollectionSubmission } from '@shared/models'; @Component({ selector: 'osf-collections-search-result-card', diff --git a/src/app/features/collections/components/collections-search-results/collections-search-results.component.ts b/src/app/features/collections/components/collections-search-results/collections-search-results.component.ts index 6511d0c90..30b43ede8 100644 --- a/src/app/features/collections/components/collections-search-results/collections-search-results.component.ts +++ b/src/app/features/collections/components/collections-search-results/collections-search-results.component.ts @@ -8,8 +8,8 @@ import { Skeleton } from 'primeng/skeleton'; import { ChangeDetectionStrategy, Component, computed } from '@angular/core'; -import { CollectionsSelectors, SetPageNumber } from '@osf/features/collections/store/collections'; import { CustomPaginatorComponent } from '@osf/shared/components'; +import { CollectionsSelectors, SetPageNumber } from '@shared/stores/collections'; import { CollectionsSearchResultCardComponent } from '../collections-search-result-card/collections-search-result-card.component'; @@ -21,7 +21,7 @@ import { CollectionsSearchResultCardComponent } from '../collections-search-resu changeDetection: ChangeDetectionStrategy.OnPush, }) export class CollectionsSearchResultsComponent { - protected searchResults = select(CollectionsSelectors.getCollectionSubmissions); + protected searchResults = select(CollectionsSelectors.getCollectionSubmissionsSearchResult); protected isCollectionDetailsLoading = select(CollectionsSelectors.getCollectionDetailsLoading); protected isCollectionSubmissionsLoading = select(CollectionsSelectors.getCollectionSubmissionsLoading); protected totalSubmissions = select(CollectionsSelectors.getTotalSubmissions); diff --git a/src/app/features/collections/models/index.ts b/src/app/features/collections/models/index.ts index 4d84b4d20..fa8df0d4a 100644 --- a/src/app/features/collections/models/index.ts +++ b/src/app/features/collections/models/index.ts @@ -1,8 +1,4 @@ export * from './collection-filter-entry.model'; export * from './collection-license-json-api.models'; -export * from './collection-submission-payload'; -export * from './collections.models'; -export * from './collections-filters.model'; -export * from './collections-json-api.models'; export * from './collections-query-params.model'; export * from './project-metadata-form.model'; diff --git a/src/app/features/collections/services/add-to-collection.service.ts b/src/app/features/collections/services/add-to-collection.service.ts index 714c743ad..3e8aaabe3 100644 --- a/src/app/features/collections/services/add-to-collection.service.ts +++ b/src/app/features/collections/services/add-to-collection.service.ts @@ -3,10 +3,8 @@ import { map, Observable } from 'rxjs'; import { inject, Injectable } from '@angular/core'; import { JsonApiService } from '@core/services'; -import { CollectionsMapper } from '@osf/features/collections/mappers'; -import { CollectionSubmissionPayload } from '@osf/features/collections/models'; -import { LicensesMapper } from '@shared/mappers'; -import { License, LicensesResponseJsonApi } from '@shared/models'; +import { CollectionsMapper, LicensesMapper } from '@shared/mappers'; +import { CollectionSubmissionPayload, License, LicensesResponseJsonApi } from '@shared/models'; import { environment } from 'src/environments/environment'; diff --git a/src/app/features/collections/services/collections-query-sync.service.ts b/src/app/features/collections/services/collections-query-sync.service.ts index c7b43a16d..4aaaecc33 100644 --- a/src/app/features/collections/services/collections-query-sync.service.ts +++ b/src/app/features/collections/services/collections-query-sync.service.ts @@ -6,14 +6,10 @@ import { ActivatedRoute, Router } from '@angular/router'; import { collectionsSortOptions } from '@osf/features/collections/constants'; import { queryParamsKeys } from '@osf/features/collections/constants/query-params-keys.const'; -import { CollectionQueryParams, CollectionsFilters } from '@osf/features/collections/models'; -import { - CollectionsSelectors, - SetAllFilters, - SetSearchValue, - SetSortBy, -} from '@osf/features/collections/store/collections'; -import { SetPageNumber } from '@osf/features/collections/store/collections/collections.actions'; +import { CollectionQueryParams } from '@osf/features/collections/models'; +import { CollectionsFilters } from '@shared/models'; +import { CollectionsSelectors, SetAllFilters, SetSearchValue, SetSortBy } from '@shared/stores/collections'; +import { SetPageNumber } from '@shared/stores/collections/collections.actions'; @Injectable() export class CollectionsQuerySyncService { diff --git a/src/app/features/collections/services/index.ts b/src/app/features/collections/services/index.ts index f02958ed8..66b8b9238 100644 --- a/src/app/features/collections/services/index.ts +++ b/src/app/features/collections/services/index.ts @@ -1,4 +1,3 @@ export * from './add-to-collection.service'; -export * from './collections.service'; export * from './collections-query-sync.service'; export * from './project-metadata-form.service'; diff --git a/src/app/features/collections/store/add-to-collection/add-to-collection.actions.ts b/src/app/features/collections/store/add-to-collection/add-to-collection.actions.ts index 3522031b5..bf91e101b 100644 --- a/src/app/features/collections/store/add-to-collection/add-to-collection.actions.ts +++ b/src/app/features/collections/store/add-to-collection/add-to-collection.actions.ts @@ -1,4 +1,4 @@ -import { CollectionSubmissionPayload } from '@osf/features/collections/models'; +import { CollectionSubmissionPayload } from '@shared/models'; export class GetCollectionLicenses { static readonly type = '[Add To Collection] Get Collection Licenses'; diff --git a/src/app/features/moderation/collection-moderation.routes.ts b/src/app/features/moderation/collection-moderation.routes.ts index 9b9bc68a1..92ef78df2 100644 --- a/src/app/features/moderation/collection-moderation.routes.ts +++ b/src/app/features/moderation/collection-moderation.routes.ts @@ -2,9 +2,11 @@ import { provideStates } from '@ngxs/store'; import { Routes } from '@angular/router'; +import { CollectionsModerationState } from '@osf/features/moderation/store/collections-moderation'; import { ResourceType } from '@osf/shared/enums'; +import { CollectionsState } from '@shared/stores/collections'; -import { ModeratorsState } from './store/moderation'; +import { ModeratorsState } from './store/moderators'; import { CollectionModerationTab } from './enums'; export const collectionModerationRoutes: Routes = [ @@ -27,6 +29,7 @@ export const collectionModerationRoutes: Routes = [ (m) => m.CollectionModerationSubmissionsComponent ), data: { tab: CollectionModerationTab.AllItems }, + providers: [provideStates([CollectionsModerationState, CollectionsState])], }, { path: 'moderators', diff --git a/src/app/features/moderation/components/add-moderator-dialog/add-moderator-dialog.component.ts b/src/app/features/moderation/components/add-moderator-dialog/add-moderator-dialog.component.ts index c1a3afd8a..40337fa7b 100644 --- a/src/app/features/moderation/components/add-moderator-dialog/add-moderator-dialog.component.ts +++ b/src/app/features/moderation/components/add-moderator-dialog/add-moderator-dialog.component.ts @@ -17,7 +17,7 @@ import { CustomPaginatorComponent, LoadingSpinnerComponent, SearchInputComponent import { AddModeratorType } from '../../enums'; import { ModeratorAddModel, ModeratorDialogAddModel } from '../../models'; -import { ClearUsers, ModeratorsSelectors, SearchUsers } from '../../store/moderation'; +import { ClearUsers, ModeratorsSelectors, SearchUsers } from '../../store/moderators'; @Component({ selector: 'osf-add-moderator-dialog', diff --git a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.html b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.html index cfcfe0e90..287c27106 100644 --- a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.html +++ b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.html @@ -1,6 +1,6 @@
- {{ totalCount }}

{{ item.label | translate }}

-
+
diff --git a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts index a919b9ab4..de025107e 100644 --- a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts +++ b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts @@ -1,13 +1,25 @@ +import { createDispatchMap, select } from '@ngxs/store'; + import { TranslatePipe } from '@ngx-translate/core'; import { SelectButton } from 'primeng/selectbutton'; -import { ChangeDetectionStrategy, Component, signal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, effect, inject, signal } from '@angular/core'; import { FormsModule } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; import { Primitive } from '@osf/core/helpers'; import { IconComponent, SelectComponent } from '@osf/shared/components'; import { ALL_SORT_OPTIONS } from '@osf/shared/constants'; +import { + ClearCollections, + ClearCollectionSubmissions, + CollectionsSelectors, + GetCollectionDetails, + GetCollectionProvider, + SearchCollectionSubmissions, + SetPageNumber, +} from '@shared/stores'; import { SUBMISSION_REVIEW_OPTIONS } from '../../constants'; import { SubmissionReviewStatus } from '../../enums'; @@ -22,15 +34,45 @@ import { pendingReviews } from '../test-data'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class CollectionModerationSubmissionsComponent { - readonly submissionReviewOptions = SUBMISSION_REVIEW_OPTIONS; + private router = inject(Router); + private route = inject(ActivatedRoute); + readonly submissionReviewOptions = SUBMISSION_REVIEW_OPTIONS; // + + protected collectionProvider = select(CollectionsSelectors.getCollectionProvider); + protected collectionDetails = select(CollectionsSelectors.getCollectionDetails); + protected providerId = signal(''); + protected primaryCollectionId = computed(() => this.collectionProvider()?.primaryCollection?.id); + + sortOptions = ALL_SORT_OPTIONS; // + selectedSortOption = signal(null); // + selectedReviewOption = this.submissionReviewOptions[0].value; // - sortOptions = ALL_SORT_OPTIONS; - selectedSortOption = signal(null); - selectedReviewOption = this.submissionReviewOptions[0].value; + protected actions = createDispatchMap({ + getCollectionProvider: GetCollectionProvider, + getCollectionDetails: GetCollectionDetails, + searchCollectionSubmissions: SearchCollectionSubmissions, + setPageNumber: SetPageNumber, + clearCollections: ClearCollections, + clearCollectionsSubmissions: ClearCollectionSubmissions, + }); - totalCount = 5; + private setupEffects(): void { + effect(() => { + const collectionId = this.primaryCollectionId(); + if (collectionId) { + this.actions.getCollectionDetails(collectionId); + } + }); + } + + totalCount = 5; // - submissions = pendingReviews; + submissions = pendingReviews; // + + constructor() { + this.initializeCollectionProvider(); + this.setupEffects(); + } changeReviewStatus(value: SubmissionReviewStatus) { console.log(value); @@ -39,4 +81,16 @@ export class CollectionModerationSubmissionsComponent { changeSort(value: Primitive) { console.log(value); } + + private initializeCollectionProvider(): void { + const id = this.route.parent?.snapshot.paramMap.get('id'); + + if (!id) { + this.router.navigate(['/not-found']); + return; + } + + this.providerId.set(id); + this.actions.getCollectionProvider(id); + } } diff --git a/src/app/features/moderation/components/moderators-list/moderators-list.component.ts b/src/app/features/moderation/components/moderators-list/moderators-list.component.ts index 1cdf5c759..6f78ba0ab 100644 --- a/src/app/features/moderation/components/moderators-list/moderators-list.component.ts +++ b/src/app/features/moderation/components/moderators-list/moderators-list.component.ts @@ -21,7 +21,7 @@ import { ModeratorsSelectors, UpdateModerator, UpdateSearchValue, -} from '@osf/features/moderation/store/moderation'; +} from '@osf/features/moderation/store/moderators'; import { SearchInputComponent } from '@osf/shared/components'; import { ResourceType } from '@osf/shared/enums'; import { CustomConfirmationService, ToastService } from '@osf/shared/services'; diff --git a/src/app/features/moderation/mappers/collection-moderation.mapper.ts b/src/app/features/moderation/mappers/collection-moderation.mapper.ts new file mode 100644 index 000000000..b8e79c3ea --- /dev/null +++ b/src/app/features/moderation/mappers/collection-moderation.mapper.ts @@ -0,0 +1,15 @@ +import { CollectionReviewAction, CollectionReviewActionJsonApiModel } from '@osf/features/moderation/models'; + +export class CollectionModerationMapper { + static fromActionResponse(action: CollectionReviewActionJsonApiModel): CollectionReviewAction { + return { + id: action.id, + type: action.type, + dateModified: action.attributes.date_modified, + fromState: action.attributes.from_state, + toState: action.attributes.to_state, + comment: action.attributes.comment, + trigger: action.attributes.trigger, + }; + } +} diff --git a/src/app/features/moderation/mappers/index.ts b/src/app/features/moderation/mappers/index.ts index 90bd110d9..b3a3c1506 100644 --- a/src/app/features/moderation/mappers/index.ts +++ b/src/app/features/moderation/mappers/index.ts @@ -1,2 +1,3 @@ +export * from './collection-moderation.mapper'; export * from './moderation.mapper'; export * from './preprint-moderation.mapper'; diff --git a/src/app/features/moderation/models/collection-review-action-json-api.model.ts b/src/app/features/moderation/models/collection-review-action-json-api.model.ts new file mode 100644 index 000000000..6938380ea --- /dev/null +++ b/src/app/features/moderation/models/collection-review-action-json-api.model.ts @@ -0,0 +1,32 @@ +export interface CollectionReviewActionJsonApiModel { + id: string; + type: 'collection-submission-actions'; + attributes: { + trigger: string; + comment: string; + from_state: string; + to_state: string; + date_created: string; + date_modified: string; + }; + relationships: { + collection: { + date: { + id: string; + type: 'collections'; + }; + }; + target: { + data: { + id: string; + type: 'collection-submission'; + }; + }; + creator: { + data: { + id: string; + type: 'users'; + }; + }; + }; +} diff --git a/src/app/features/moderation/models/collection-review-action.model.ts b/src/app/features/moderation/models/collection-review-action.model.ts new file mode 100644 index 000000000..b5200af04 --- /dev/null +++ b/src/app/features/moderation/models/collection-review-action.model.ts @@ -0,0 +1,9 @@ +export interface CollectionReviewAction { + id: string; + type: string; + dateModified: string; + fromState: string; + toState: string; + comment: string; + trigger: string; +} diff --git a/src/app/features/moderation/models/index.ts b/src/app/features/moderation/models/index.ts index 38e717b41..26f80be84 100644 --- a/src/app/features/moderation/models/index.ts +++ b/src/app/features/moderation/models/index.ts @@ -1,3 +1,5 @@ +export * from './collection-review-action.model'; +export * from './collection-review-action-json-api.model'; export * from './invite-moderator-form.model'; export * from './moderator.model'; export * from './moderator-add.model'; diff --git a/src/app/features/moderation/preprint-moderation.routes.ts b/src/app/features/moderation/preprint-moderation.routes.ts index ef2a0053f..a254b9aa4 100644 --- a/src/app/features/moderation/preprint-moderation.routes.ts +++ b/src/app/features/moderation/preprint-moderation.routes.ts @@ -4,7 +4,7 @@ import { Routes } from '@angular/router'; import { ResourceType } from '@osf/shared/enums'; -import { ModeratorsState } from './store/moderation'; +import { ModeratorsState } from './store/moderators'; import { PreprintModerationState } from './store/preprint-moderation'; import { PreprintModerationTab } from './enums'; diff --git a/src/app/features/moderation/registry-moderation.routes.ts b/src/app/features/moderation/registry-moderation.routes.ts index d78fded4c..d4c98106a 100644 --- a/src/app/features/moderation/registry-moderation.routes.ts +++ b/src/app/features/moderation/registry-moderation.routes.ts @@ -4,7 +4,7 @@ import { Routes } from '@angular/router'; import { ResourceType } from '@osf/shared/enums'; -import { ModeratorsState } from './store/moderation'; +import { ModeratorsState } from './store/moderators'; import { RegistryModerationTab } from './enums'; export const registryModerationRoutes: Routes = [ diff --git a/src/app/features/moderation/store/collections-moderation/collections-moderation.actions.ts b/src/app/features/moderation/store/collections-moderation/collections-moderation.actions.ts new file mode 100644 index 000000000..788942e59 --- /dev/null +++ b/src/app/features/moderation/store/collections-moderation/collections-moderation.actions.ts @@ -0,0 +1,10 @@ +export class GetCollectionSubmissions { + static readonly type = '[Collections Moderation] Get Collection Submissions'; + + constructor( + public collectionId: string, + public status: string, + public page: string, + public sortBy: string + ) {} +} diff --git a/src/app/features/moderation/store/collections-moderation/collections-moderation.model.ts b/src/app/features/moderation/store/collections-moderation/collections-moderation.model.ts new file mode 100644 index 000000000..5860bb6a8 --- /dev/null +++ b/src/app/features/moderation/store/collections-moderation/collections-moderation.model.ts @@ -0,0 +1,21 @@ +import { CollectionReviewAction } from '@osf/features/moderation/models'; +import { AsyncStateModel, AsyncStateWithTotalCount, CollectionSubmission } from '@shared/models'; + +export interface CollectionsModerationStateModel { + collectionSubmissions: AsyncStateModel; + reviewActions: AsyncStateWithTotalCount; +} + +export const COLLECTIONS_MODERATION_STATE_DEFAULTS: CollectionsModerationStateModel = { + collectionSubmissions: { + data: [], + isLoading: false, + error: null, + }, + reviewActions: { + data: [], + isLoading: false, + error: null, + totalCount: 0, + }, +}; diff --git a/src/app/features/moderation/store/collections-moderation/collections-moderation.selectors.ts b/src/app/features/moderation/store/collections-moderation/collections-moderation.selectors.ts new file mode 100644 index 000000000..185e04df0 --- /dev/null +++ b/src/app/features/moderation/store/collections-moderation/collections-moderation.selectors.ts @@ -0,0 +1,11 @@ +import { Selector } from '@ngxs/store'; + +import { CollectionsModerationStateModel } from './collections-moderation.model'; +import { CollectionsModerationState } from './collections-moderation.state'; + +export class CollectionsModerationSelectors { + @Selector([CollectionsModerationState]) + static getCollectionSubmissions(state: CollectionsModerationStateModel) { + return state.collectionSubmissions.data; + } +} diff --git a/src/app/features/moderation/store/collections-moderation/collections-moderation.state.ts b/src/app/features/moderation/store/collections-moderation/collections-moderation.state.ts new file mode 100644 index 000000000..07efff5b8 --- /dev/null +++ b/src/app/features/moderation/store/collections-moderation/collections-moderation.state.ts @@ -0,0 +1,47 @@ +import { Action, State, StateContext } from '@ngxs/store'; + +import { catchError, tap } from 'rxjs'; + +import { inject, Injectable } from '@angular/core'; + +import { handleSectionError } from '@core/handlers'; +import { CollectionsService } from '@shared/services'; +import { CollectionsStateModel } from '@shared/stores'; + +import { GetCollectionSubmissions } from './collections-moderation.actions'; +import { COLLECTIONS_MODERATION_STATE_DEFAULTS, CollectionsModerationStateModel } from './collections-moderation.model'; + +@State({ + name: 'collectionsModeration', + defaults: COLLECTIONS_MODERATION_STATE_DEFAULTS, +}) +@Injectable() +export class CollectionsModerationState { + collectionsService = inject(CollectionsService); + + @Action(GetCollectionSubmissions) + getCollectionSubmissions(ctx: StateContext, action: GetCollectionSubmissions) { + const state = ctx.getState(); + ctx.patchState({ + collectionSubmissions: { + ...state.collectionSubmissions, + isLoading: true, + }, + }); + + return this.collectionsService + .fetchCollectionSubmissions(action.collectionId, action.status, action.page, action.sortBy) + .pipe( + tap((res) => { + ctx.patchState({ + collectionSubmissions: { + data: res, + isLoading: false, + error: null, + }, + }); + }), + catchError((error) => handleSectionError(ctx, 'collectionSubmissions', error)) + ); + } +} diff --git a/src/app/features/moderation/store/collections-moderation/index.ts b/src/app/features/moderation/store/collections-moderation/index.ts new file mode 100644 index 000000000..12bb7c820 --- /dev/null +++ b/src/app/features/moderation/store/collections-moderation/index.ts @@ -0,0 +1,4 @@ +export * from './collections-moderation.actions'; +export * from './collections-moderation.model'; +export * from './collections-moderation.selectors'; +export * from './collections-moderation.state'; diff --git a/src/app/features/moderation/store/moderation/index.ts b/src/app/features/moderation/store/moderators/index.ts similarity index 100% rename from src/app/features/moderation/store/moderation/index.ts rename to src/app/features/moderation/store/moderators/index.ts diff --git a/src/app/features/moderation/store/moderation/moderators.actions.ts b/src/app/features/moderation/store/moderators/moderators.actions.ts similarity index 100% rename from src/app/features/moderation/store/moderation/moderators.actions.ts rename to src/app/features/moderation/store/moderators/moderators.actions.ts diff --git a/src/app/features/moderation/store/moderation/moderators.model.ts b/src/app/features/moderation/store/moderators/moderators.model.ts similarity index 100% rename from src/app/features/moderation/store/moderation/moderators.model.ts rename to src/app/features/moderation/store/moderators/moderators.model.ts diff --git a/src/app/features/moderation/store/moderation/moderators.selectors.ts b/src/app/features/moderation/store/moderators/moderators.selectors.ts similarity index 100% rename from src/app/features/moderation/store/moderation/moderators.selectors.ts rename to src/app/features/moderation/store/moderators/moderators.selectors.ts diff --git a/src/app/features/moderation/store/moderation/moderators.state.ts b/src/app/features/moderation/store/moderators/moderators.state.ts similarity index 100% rename from src/app/features/moderation/store/moderation/moderators.state.ts rename to src/app/features/moderation/store/moderators/moderators.state.ts diff --git a/src/app/features/my-projects/services/my-projects.service.ts b/src/app/features/my-projects/services/my-projects.service.ts index 9effb9750..2f19ac38e 100644 --- a/src/app/features/my-projects/services/my-projects.service.ts +++ b/src/app/features/my-projects/services/my-projects.service.ts @@ -5,7 +5,6 @@ import { inject, Injectable } from '@angular/core'; import { JsonApiResponse } from '@core/models'; import { JsonApiService } from '@osf/core/services'; -import { SparseCollectionsResponseJsonApi } from '@osf/features/collections/models'; import { ResourceType, SortOrder } from '@osf/shared/enums'; import { CreateProjectPayloadJsoApi, NodeResponseModel, UpdateNodeRequestModel } from '@shared/models'; @@ -89,21 +88,6 @@ export class MyProjectsService { return this.getMyItems('nodes', filters, pageNumber, pageSize, 'nodes'); } - getBookmarksCollectionId(): Observable { - const params: Record = { - 'fields[collections]': 'title,bookmarks', - }; - - return this.jsonApiService.get(environment.apiUrl + '/collections/', params).pipe( - map((response) => { - const bookmarksCollection = response.data.find( - (collection) => collection.attributes.title === 'Bookmarks' && collection.attributes.bookmarks - ); - return bookmarksCollection?.id ?? ''; - }) - ); - } - getMyRegistrations( filters?: MyProjectsSearchFilters, pageNumber?: number, diff --git a/src/app/features/preprints/components/filters/preprints-resources/preprints-resources.component.ts b/src/app/features/preprints/components/filters/preprints-resources/preprints-resources.component.ts index ba8f0a726..a41ca2655 100644 --- a/src/app/features/preprints/components/filters/preprints-resources/preprints-resources.component.ts +++ b/src/app/features/preprints/components/filters/preprints-resources/preprints-resources.component.ts @@ -11,7 +11,6 @@ import { toSignal } from '@angular/core/rxjs-interop'; import { FormsModule } from '@angular/forms'; import { Primitive } from '@core/helpers'; -import { SetSortBy } from '@osf/features/collections/store/collections'; import { GetResourcesByLink } from '@osf/features/my-profile/store'; import { PreprintsFilterChipsComponent, PreprintsResourcesFiltersComponent } from '@osf/features/preprints/components'; import { PreprintsDiscoverSelectors } from '@osf/features/preprints/store/preprints-discover'; @@ -20,6 +19,7 @@ import { PreprintsResourcesFiltersOptionsSelectors } from '@osf/features/preprin import { ResourceCardComponent } from '@osf/shared/components'; import { searchSortingOptions } from '@osf/shared/constants'; import { IS_WEB, IS_XSMALL } from '@osf/shared/utils'; +import { SetSortBy } from '@shared/stores/collections'; @Component({ selector: 'osf-preprints-resources', diff --git a/src/app/features/project/overview/project-overview.component.ts b/src/app/features/project/overview/project-overview.component.ts index fdfed3824..b9976ce8d 100644 --- a/src/app/features/project/overview/project-overview.component.ts +++ b/src/app/features/project/overview/project-overview.component.ts @@ -9,11 +9,11 @@ import { ChangeDetectionStrategy, Component, computed, DestroyRef, HostBinding, import { FormsModule } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; -import { ClearCollections } from '@osf/features/collections/store/collections'; import { LoadingSpinnerComponent, ResourceMetadataComponent, SubHeaderComponent } from '@shared/components'; import { ResourceType } from '@shared/enums'; import { MapProjectOverview } from '@shared/mappers/resource-overview.mappers'; import { GetBookmarksCollectionId } from '@shared/stores'; +import { ClearCollections } from '@shared/stores/collections'; import { ClearWiki, GetHomeWiki } from '../wiki/store'; diff --git a/src/app/features/collections/mappers/collections.mapper.ts b/src/app/shared/mappers/collections/collections.mapper.ts similarity index 97% rename from src/app/features/collections/mappers/collections.mapper.ts rename to src/app/shared/mappers/collections/collections.mapper.ts index e75bcdc9b..eccd8b034 100644 --- a/src/app/features/collections/mappers/collections.mapper.ts +++ b/src/app/shared/mappers/collections/collections.mapper.ts @@ -8,8 +8,8 @@ import { CollectionSubmission, CollectionSubmissionJsonApi, CollectionSubmissionPayload, -} from '@osf/features/collections/models'; -import { CollectionSubmissionPayloadJsonApi } from '@osf/features/collections/models/collection-submission-payload-json-api.model'; + CollectionSubmissionPayloadJsonApi, +} from '@osf/shared/models'; import { convertToSnakeCase } from '@shared/utils'; export class CollectionsMapper { diff --git a/src/app/features/collections/mappers/index.ts b/src/app/shared/mappers/collections/index.ts similarity index 100% rename from src/app/features/collections/mappers/index.ts rename to src/app/shared/mappers/collections/index.ts diff --git a/src/app/shared/mappers/index.ts b/src/app/shared/mappers/index.ts index 41661151c..e81252b50 100644 --- a/src/app/shared/mappers/index.ts +++ b/src/app/shared/mappers/index.ts @@ -1,4 +1,5 @@ export * from './addon.mapper'; +export * from './collections'; export * from './contributors'; export * from './filters'; export * from './institutions'; diff --git a/src/app/features/collections/models/collection-submission-payload-json-api.model.ts b/src/app/shared/models/collections/collection-submission-payload-json-api.model.ts similarity index 100% rename from src/app/features/collections/models/collection-submission-payload-json-api.model.ts rename to src/app/shared/models/collections/collection-submission-payload-json-api.model.ts diff --git a/src/app/features/collections/models/collection-submission-payload.ts b/src/app/shared/models/collections/collection-submission-payload.model.ts similarity index 100% rename from src/app/features/collections/models/collection-submission-payload.ts rename to src/app/shared/models/collections/collection-submission-payload.model.ts diff --git a/src/app/features/collections/models/collections-filters.model.ts b/src/app/shared/models/collections/collections-filters.model.ts similarity index 100% rename from src/app/features/collections/models/collections-filters.model.ts rename to src/app/shared/models/collections/collections-filters.model.ts diff --git a/src/app/features/collections/models/collections-json-api.models.ts b/src/app/shared/models/collections/collections-json-api.models.ts similarity index 98% rename from src/app/features/collections/models/collections-json-api.models.ts rename to src/app/shared/models/collections/collections-json-api.models.ts index 534bad4f1..0f713998f 100644 --- a/src/app/features/collections/models/collections-json-api.models.ts +++ b/src/app/shared/models/collections/collections-json-api.models.ts @@ -127,7 +127,7 @@ export interface CollectionDetailsGetResponseJsonApi extends JsonApiResponse { + const params: Record = { + page, + 'filter[reviews_state]': status, + 'page[size]': '10', + sort: sortBy, + }; + + return this.jsonApiService + .get< + JsonApiResponse + >(`${environment.apiUrl}/collections/${collectionId}/collection_submissions/`, params) + .pipe( + map((response) => { + return CollectionsMapper.fromGetCollectionSubmissionsResponse(response.data); + }) + ); + } + fetchAllUserCollectionSubmissions(providerId: string, projectIds: string[]): Observable { const pendingSubmissions$ = this.fetchUserCollectionSubmissionsByStatus(providerId, projectIds, 'pending'); const acceptedSubmissions$ = this.fetchUserCollectionSubmissionsByStatus(providerId, projectIds, 'accepted'); diff --git a/src/app/shared/services/index.ts b/src/app/shared/services/index.ts index cd9230ebd..5c36d13ca 100644 --- a/src/app/shared/services/index.ts +++ b/src/app/shared/services/index.ts @@ -1,6 +1,7 @@ export * from './addons'; export { BookmarksService } from './bookmarks.service'; export { BrandService } from './brand.service'; +export { CollectionsService } from './collections.service'; export { ContributorsService } from './contributors.service'; export { CustomConfirmationService } from './custom-confirmation.service'; export { FilesService } from './files.service'; diff --git a/src/app/features/collections/store/collections/collections.actions.ts b/src/app/shared/stores/collections/collections.actions.ts similarity index 97% rename from src/app/features/collections/store/collections/collections.actions.ts rename to src/app/shared/stores/collections/collections.actions.ts index 665b9ccd1..03f289aef 100644 --- a/src/app/features/collections/store/collections/collections.actions.ts +++ b/src/app/shared/stores/collections/collections.actions.ts @@ -1,4 +1,4 @@ -import { CollectionsFilters } from '@osf/features/collections/models'; +import { CollectionsFilters } from '@shared/models'; export class GetCollectionProvider { static readonly type = '[Collections] Get Collection Provider'; diff --git a/src/app/features/collections/store/collections/collections.model.ts b/src/app/shared/stores/collections/collections.model.ts similarity index 78% rename from src/app/features/collections/store/collections/collections.model.ts rename to src/app/shared/stores/collections/collections.model.ts index e0b0b64b4..219e1dbad 100644 --- a/src/app/features/collections/store/collections/collections.model.ts +++ b/src/app/shared/stores/collections/collections.model.ts @@ -1,9 +1,4 @@ -import { - CollectionDetails, - CollectionProvider, - CollectionsFilters, - CollectionSubmission, -} from '@osf/features/collections/models'; +import { CollectionDetails, CollectionProvider, CollectionsFilters, CollectionSubmission } from '@shared/models'; import { AsyncStateModel } from '@shared/models/store'; export interface CollectionsStateModel { diff --git a/src/app/features/collections/store/collections/collections.selectors.ts b/src/app/shared/stores/collections/collections.selectors.ts similarity index 93% rename from src/app/features/collections/store/collections/collections.selectors.ts rename to src/app/shared/stores/collections/collections.selectors.ts index f312e9f6b..047937a55 100644 --- a/src/app/features/collections/store/collections/collections.selectors.ts +++ b/src/app/shared/stores/collections/collections.selectors.ts @@ -1,6 +1,6 @@ import { Selector } from '@ngxs/store'; -import { CollectionsFilters } from '@osf/features/collections/models'; +import { CollectionsFilters } from '@shared/models'; import { CollectionsStateModel } from './collections.model'; import { CollectionsState } from './collections.state'; @@ -37,7 +37,7 @@ export class CollectionsSelectors { } @Selector([CollectionsState]) - static getCollectionSubmissions(state: CollectionsStateModel) { + static getCollectionSubmissionsSearchResult(state: CollectionsStateModel) { return state.collectionSubmissions.data; } diff --git a/src/app/features/collections/store/collections/collections.state.ts b/src/app/shared/stores/collections/collections.state.ts similarity index 99% rename from src/app/features/collections/store/collections/collections.state.ts rename to src/app/shared/stores/collections/collections.state.ts index ba669284f..b58cabad0 100644 --- a/src/app/features/collections/store/collections/collections.state.ts +++ b/src/app/shared/stores/collections/collections.state.ts @@ -4,7 +4,7 @@ import { catchError, tap, throwError } from 'rxjs'; import { inject, Injectable } from '@angular/core'; -import { CollectionsService } from '@osf/features/collections/services'; +import { CollectionsService } from '@shared/services'; import { ClearCollections, diff --git a/src/app/features/collections/store/collections/index.ts b/src/app/shared/stores/collections/index.ts similarity index 100% rename from src/app/features/collections/store/collections/index.ts rename to src/app/shared/stores/collections/index.ts diff --git a/src/app/shared/stores/index.ts b/src/app/shared/stores/index.ts index c1cd0cb36..7cb2b4495 100644 --- a/src/app/shared/stores/index.ts +++ b/src/app/shared/stores/index.ts @@ -1,5 +1,6 @@ export * from './addons'; export * from './bookmarks'; +export * from './collections'; export * from './contributors'; export * from './institutions'; export * from './institutions-search'; From c5b4ad902299954e4132163fa1543be4c27f48e2 Mon Sep 17 00:00:00 2001 From: Roman Nastyuk Date: Wed, 30 Jul 2025 14:36:09 +0300 Subject: [PATCH 2/7] feat(collection-moderation): integrated collection moderation api --- .../collections/collections.routes.ts | 13 +- ...tion-moderation-submissions.component.html | 19 ++- ...ection-moderation-submissions.component.ts | 136 ++++++++++++++++-- ...lection-submission-overview.component.html | 3 + ...lection-submission-overview.component.scss | 5 + ...tion-submission-overview.component.spec.ts | 22 +++ ...ollection-submission-overview.component.ts | 51 +++++++ .../moderators-list.component.ts | 4 +- .../registry-submissions.component.html | 2 +- .../submission-item.component.html | 66 +++++++-- .../submission-item.component.ts | 71 ++++++++- .../submissions-list.component.html | 20 ++- .../submissions-list.component.ts | 13 +- .../moderation/constants/submission.const.ts | 6 +- .../moderation/enums/moderation-type.enum.ts | 1 + .../enums/submission-review-status.enum.ts | 1 + .../mappers/collection-moderation.mapper.ts | 15 -- src/app/features/moderation/mappers/index.ts | 1 - .../models/collection-review-action.model.ts | 9 -- ...tion-submission-review-action-json.api.ts} | 11 +- ...llection-submission-review-action.model.ts | 13 ++ src/app/features/moderation/models/index.ts | 4 +- .../moderation/models/submission.model.ts | 4 +- .../collection-moderation.component.scss | 4 +- .../collections-moderation.actions.ts | 34 +++++ .../collections-moderation.model.ts | 13 +- .../collections-moderation.selectors.ts | 35 +++++ .../collections-moderation.state.ts | 86 ++++++++++- .../linked-projects.component.html | 4 +- .../linked-projects.component.ts | 3 +- .../overview-components.component.html | 12 +- .../overview-components.component.ts | 3 +- .../overview-toolbar.component.html | 18 ++- .../overview-toolbar.component.ts | 1 + .../overview/project-overview.component.html | 55 ++++++- .../overview/project-overview.component.ts | 86 ++++++++++- src/app/features/project/project.routes.ts | 4 +- src/app/shared/components/index.ts | 1 + .../make-decision-dialog.component.html | 93 ++++++++++++ .../make-decision-dialog.component.scss | 0 .../make-decision-dialog.component.spec.ts | 22 +++ .../make-decision-dialog.component.ts | 94 ++++++++++++ .../resource-metadata.component.html | 12 +- .../resource-metadata.component.ts | 1 + .../sub-header/sub-header.component.html | 8 +- .../sub-header/sub-header.component.ts | 3 +- .../shared/constants/sort-options.const.ts | 19 +++ src/app/shared/enums/index.ts | 2 + .../moderation-decision-form-controls.enum.ts | 4 + .../enums/moderation-submit-type.enum.ts | 5 + src/app/shared/enums/sort-type.enum.ts | 2 + .../mappers/collections/collections.mapper.ts | 103 ++++++++++--- .../collections-json-api.models.ts | 13 ++ .../models/collections/collections.models.ts | 4 + src/app/shared/models/collections/index.ts | 2 + .../review-action-payload-json-api.model.ts | 17 +++ .../review-action-payload.model.ts | 5 + src/app/shared/pipes/time-ago.pipe.ts | 41 ++++++ .../shared/services/collections.service.ts | 48 +++++-- src/assets/i18n/en.json | 18 +++ src/assets/styles/overrides/button.scss | 14 ++ src/assets/styles/overrides/message.scss | 6 + 62 files changed, 1232 insertions(+), 153 deletions(-) create mode 100644 src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.html create mode 100644 src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.scss create mode 100644 src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.spec.ts create mode 100644 src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.ts delete mode 100644 src/app/features/moderation/mappers/collection-moderation.mapper.ts delete mode 100644 src/app/features/moderation/models/collection-review-action.model.ts rename src/app/features/moderation/models/{collection-review-action-json-api.model.ts => collection-submission-review-action-json.api.ts} (74%) create mode 100644 src/app/features/moderation/models/collection-submission-review-action.model.ts create mode 100644 src/app/shared/components/make-decision-dialog/make-decision-dialog.component.html create mode 100644 src/app/shared/components/make-decision-dialog/make-decision-dialog.component.scss create mode 100644 src/app/shared/components/make-decision-dialog/make-decision-dialog.component.spec.ts create mode 100644 src/app/shared/components/make-decision-dialog/make-decision-dialog.component.ts create mode 100644 src/app/shared/enums/moderation-decision-form-controls.enum.ts create mode 100644 src/app/shared/enums/moderation-submit-type.enum.ts create mode 100644 src/app/shared/models/collections/review-action-payload-json-api.model.ts create mode 100644 src/app/shared/models/collections/review-action-payload.model.ts create mode 100644 src/app/shared/pipes/time-ago.pipe.ts diff --git a/src/app/features/collections/collections.routes.ts b/src/app/features/collections/collections.routes.ts index 41f8178c8..db9c2a9d3 100644 --- a/src/app/features/collections/collections.routes.ts +++ b/src/app/features/collections/collections.routes.ts @@ -3,8 +3,9 @@ import { provideStates } from '@ngxs/store'; import { Routes } from '@angular/router'; import { AddToCollectionState } from '@osf/features/collections/store/add-to-collection'; +import { CollectionsModerationState } from '@osf/features/moderation/store/collections-moderation'; import { ConfirmLeavingGuard } from '@shared/guards'; -import { ContributorsState, ProjectsState } from '@shared/stores'; +import { BookmarksState, ContributorsState, ProjectsState } from '@shared/stores'; import { CollectionsState } from '@shared/stores/collections'; export const collectionsRoutes: Routes = [ @@ -42,10 +43,18 @@ export const collectionsRoutes: Routes = [ canDeactivate: [ConfirmLeavingGuard], }, { - path: ':id/moderation', + path: ':collectionId/moderation', loadChildren: () => import('@osf/features/moderation/collection-moderation.routes').then((m) => m.collectionModerationRoutes), }, + { + path: ':collectionId/moderation/:id', + loadComponent: () => + import( + '@osf/features/moderation/components/collection-submission-overview/collection-submission-overview.component' + ).then((mod) => mod.CollectionSubmissionOverviewComponent), + providers: [provideStates([CollectionsModerationState, CollectionsState, BookmarksState])], + }, ], }, ]; diff --git a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.html b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.html index 287c27106..ceb8eba7f 100644 --- a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.html +++ b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.html @@ -3,14 +3,13 @@ -

{{ totalCount }}

{{ item.label | translate }}

@@ -27,4 +26,18 @@
- +@if (!isSubmissionsLoading() && !isReviewActionsLoading()) { + + + @if (collectionSubmissions().length) { + + } +} @else { + +} diff --git a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts index de025107e..3723be9bf 100644 --- a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts +++ b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts @@ -2,6 +2,7 @@ import { createDispatchMap, select } from '@ngxs/store'; import { TranslatePipe } from '@ngx-translate/core'; +import { PaginatorState } from 'primeng/paginator'; import { SelectButton } from 'primeng/selectbutton'; import { ChangeDetectionStrategy, Component, computed, effect, inject, signal } from '@angular/core'; @@ -9,8 +10,18 @@ import { FormsModule } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { Primitive } from '@osf/core/helpers'; -import { IconComponent, SelectComponent } from '@osf/shared/components'; -import { ALL_SORT_OPTIONS } from '@osf/shared/constants'; +import { + CollectionsModerationSelectors, + GetCollectionSubmissions, + GetSubmissionsReviewActions, +} from '@osf/features/moderation/store/collections-moderation'; +import { + CustomPaginatorComponent, + IconComponent, + LoadingSpinnerComponent, + SelectComponent, +} from '@osf/shared/components'; +import { COLLECTION_SUBMISSIONS_SORT_OPTIONS } from '@osf/shared/constants'; import { ClearCollections, ClearCollectionSubmissions, @@ -24,11 +35,19 @@ import { import { SUBMISSION_REVIEW_OPTIONS } from '../../constants'; import { SubmissionReviewStatus } from '../../enums'; import { SubmissionsListComponent } from '../submissions-list/submissions-list.component'; -import { pendingReviews } from '../test-data'; @Component({ selector: 'osf-collection-moderation-submissions', - imports: [SelectButton, TranslatePipe, FormsModule, SelectComponent, SubmissionsListComponent, IconComponent], + imports: [ + SelectButton, + TranslatePipe, + FormsModule, + SelectComponent, + SubmissionsListComponent, + IconComponent, + CustomPaginatorComponent, + LoadingSpinnerComponent, + ], templateUrl: './collection-moderation-submissions.component.html', styleUrl: './collection-moderation-submissions.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, @@ -36,24 +55,33 @@ import { pendingReviews } from '../test-data'; export class CollectionModerationSubmissionsComponent { private router = inject(Router); private route = inject(ActivatedRoute); - readonly submissionReviewOptions = SUBMISSION_REVIEW_OPTIONS; // + readonly submissionReviewOptions = SUBMISSION_REVIEW_OPTIONS; protected collectionProvider = select(CollectionsSelectors.getCollectionProvider); protected collectionDetails = select(CollectionsSelectors.getCollectionDetails); + protected collectionSubmissions = select(CollectionsModerationSelectors.getCollectionSubmissions); + protected totalSubmissions = select(CollectionsModerationSelectors.getCollectionSubmissionsTotalCount); + protected isSubmissionsLoading = select(CollectionsModerationSelectors.getCollectionSubmissionsLoading); + protected isReviewActionsLoading = select(CollectionsModerationSelectors.getReviewActionsLoading); protected providerId = signal(''); protected primaryCollectionId = computed(() => this.collectionProvider()?.primaryCollection?.id); + protected reviewStatus = signal(SubmissionReviewStatus.Pending); + protected currentPage = signal('1'); + + sortOptions = COLLECTION_SUBMISSIONS_SORT_OPTIONS; + selectedSortOption = signal(this.sortOptions[0].value); - sortOptions = ALL_SORT_OPTIONS; // - selectedSortOption = signal(null); // - selectedReviewOption = this.submissionReviewOptions[0].value; // + protected firstIndex = computed(() => (parseInt(this.currentPage()) - 1) * 10); protected actions = createDispatchMap({ getCollectionProvider: GetCollectionProvider, getCollectionDetails: GetCollectionDetails, searchCollectionSubmissions: SearchCollectionSubmissions, + getCollectionSubmissions: GetCollectionSubmissions, setPageNumber: SetPageNumber, clearCollections: ClearCollections, clearCollectionsSubmissions: ClearCollectionSubmissions, + getSubmissionsReviewActions: GetSubmissionsReviewActions, }); private setupEffects(): void { @@ -63,27 +91,107 @@ export class CollectionModerationSubmissionsComponent { this.actions.getCollectionDetails(collectionId); } }); - } - totalCount = 5; // + effect(() => { + const collectionDetails = this.collectionDetails(); + const status = this.reviewStatus(); + const sortBy = this.selectedSortOption() || this.sortOptions[0].value; + const page = this.currentPage(); + + if (collectionDetails && status) { + this.actions.getCollectionSubmissions(collectionDetails.id, status, page, sortBy); + } + }); - submissions = pendingReviews; // + effect(() => { + const status = this.reviewStatus(); + const sortBy = this.selectedSortOption(); + const page = this.currentPage(); + + this.updateUrlParams(status, sortBy, page); + }); + + effect(() => { + const submissions = this.collectionSubmissions(); + const collectionId = this.primaryCollectionId(); + + if (submissions.length && collectionId) { + submissions.forEach((submission) => { + this.actions.getSubmissionsReviewActions(submission.id, collectionId); + }); + } + }); + } constructor() { + this.initializeFromQueryParams(); this.initializeCollectionProvider(); this.setupEffects(); } changeReviewStatus(value: SubmissionReviewStatus) { - console.log(value); + this.reviewStatus.set(value); + this.currentPage.set('1'); } changeSort(value: Primitive) { - console.log(value); + this.selectedSortOption.set(value as string); + this.currentPage.set('1'); + } + + onPageChange(event: PaginatorState): void { + if (event.page !== undefined) { + const pageNumber = (event.page + 1).toString(); + this.currentPage.set(pageNumber); + } + } + + private initializeFromQueryParams(): void { + const queryParams = this.route.snapshot.queryParams; + const statusValues = Object.values(SubmissionReviewStatus); + + const statusParam = queryParams['status']; + if (statusParam && statusValues.includes(statusParam)) { + this.reviewStatus.set(statusParam); + } else { + this.reviewStatus.set(SubmissionReviewStatus.Pending); + } + + const sortByParam = queryParams['sortBy']; + if (sortByParam) { + this.selectedSortOption.set(sortByParam); + } + + const pageParam = queryParams['page']; + if (pageParam && !isNaN(parseInt(pageParam)) && parseInt(pageParam)) { + this.currentPage.set(pageParam); + } else { + this.currentPage.set('1'); + } + } + + private updateUrlParams(status: SubmissionReviewStatus, sortBy: string, page: string): void { + const queryParams: Record = { status }; + + if (sortBy) { + queryParams['sortBy'] = sortBy; + } + + if (page !== '1') { + queryParams['page'] = page; + } else { + queryParams['page'] = null; + } + + this.router.navigate([], { + relativeTo: this.route, + queryParams, + queryParamsHandling: 'merge', + }); } private initializeCollectionProvider(): void { - const id = this.route.parent?.snapshot.paramMap.get('id'); + const id = this.route.parent?.snapshot.paramMap.get('collectionId'); if (!id) { this.router.navigate(['/not-found']); diff --git a/src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.html b/src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.html new file mode 100644 index 000000000..0659eace7 --- /dev/null +++ b/src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.scss b/src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.scss new file mode 100644 index 000000000..da0c027b5 --- /dev/null +++ b/src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.scss @@ -0,0 +1,5 @@ +:host { + display: flex; + flex-direction: column; + flex: 1; +} diff --git a/src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.spec.ts b/src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.spec.ts new file mode 100644 index 000000000..29571332c --- /dev/null +++ b/src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CollectionSubmissionOverviewComponent } from './collection-submission-overview.component'; + +describe('CollectionSubmissionOverviewComponent', () => { + let component: CollectionSubmissionOverviewComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CollectionSubmissionOverviewComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(CollectionSubmissionOverviewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.ts b/src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.ts new file mode 100644 index 000000000..ab7c50033 --- /dev/null +++ b/src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.ts @@ -0,0 +1,51 @@ +import { createDispatchMap, select } from '@ngxs/store'; + +import { ChangeDetectionStrategy, Component, effect, inject } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; + +import { ProjectOverviewComponent } from '@osf/features/project/overview/project-overview.component'; +import { CollectionSubmission } from '@shared/models'; + +import { CollectionsModerationSelectors, SetCurrentSubmission } from '../../store/collections-moderation'; + +@Component({ + selector: 'osf-collection-submission-overview', + imports: [ProjectOverviewComponent], + templateUrl: './collection-submission-overview.component.html', + styleUrl: './collection-submission-overview.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class CollectionSubmissionOverviewComponent { + private route = inject(ActivatedRoute); + private router = inject(Router); + private activatedRoute = inject(ActivatedRoute); + protected submissions = select(CollectionsModerationSelectors.getCollectionSubmissions); + + protected actions = createDispatchMap({ + setCurrentSubmission: SetCurrentSubmission, + }); + + constructor() { + effect(() => { + const submissions = this.submissions(); + if (!submissions.length) { + this.router.navigate(['../'], { relativeTo: this.activatedRoute }); + } + }); + + effect(() => { + const submissionId = this.route.snapshot.params['id']; + const submissions = this.submissions(); + + if (!submissionId || !submissions) return; + + const currentSubmission = submissions.find( + (submission: CollectionSubmission) => submission.nodeId === submissionId + ); + + if (currentSubmission) { + this.actions.setCurrentSubmission(currentSubmission); + } + }); + } +} diff --git a/src/app/features/moderation/components/moderators-list/moderators-list.component.ts b/src/app/features/moderation/components/moderators-list/moderators-list.component.ts index 6f78ba0ab..0d930b4d1 100644 --- a/src/app/features/moderation/components/moderators-list/moderators-list.component.ts +++ b/src/app/features/moderation/components/moderators-list/moderators-list.component.ts @@ -48,7 +48,9 @@ export class ModeratorsListComponent implements OnInit { private readonly dialogService = inject(DialogService); private readonly toastService = inject(ToastService); - readonly providerId = toSignal(this.route.parent?.params.pipe(map((params) => params['id'])) ?? of(undefined)); + readonly providerId = toSignal( + this.route.parent?.params.pipe(map((params) => params['collectionId'])) ?? of(undefined) + ); readonly resourceType: Signal = toSignal( this.route.data.pipe(map((params) => params['resourceType'])) ?? of(undefined) ); diff --git a/src/app/features/moderation/components/registry-submissions/registry-submissions.component.html b/src/app/features/moderation/components/registry-submissions/registry-submissions.component.html index 4e63c0b8a..dd993ea4a 100644 --- a/src/app/features/moderation/components/registry-submissions/registry-submissions.component.html +++ b/src/app/features/moderation/components/registry-submissions/registry-submissions.component.html @@ -27,4 +27,4 @@ - + diff --git a/src/app/features/moderation/components/submission-item/submission-item.component.html b/src/app/features/moderation/components/submission-item/submission-item.component.html index 6d0b41388..edf5702ba 100644 --- a/src/app/features/moderation/components/submission-item/submission-item.component.html +++ b/src/app/features/moderation/components/submission-item/submission-item.component.html @@ -1,16 +1,52 @@ -
-
- - {{ submission().name }} -
+@let action = currentReviewAction(); +@let attributes = currentSubmissionAttributes(); + +@if (action && attributes) { +
+
+ + + +

{{ submission().title }}

+
+
-

- {{ 'moderation.submissionReview.submitted' | translate }} - {{ submission().dateSubmitted }} - {{ 'moderation.submissionReview.by' | translate }} - {{ submission().submittedBy }} -

-
+

+ @switch (action.toState) { + @case (SubmissionReviewStatus.Pending) { + {{ 'moderation.submissionReview.submitted' | translate }} + } + @case (SubmissionReviewStatus.Accepted) { + {{ 'moderation.submissionReview.accepted' | translate }} + } + @case (SubmissionReviewStatus.Rejected) { + {{ 'moderation.submissionReview.rejected' | translate }} + } + @case (SubmissionReviewStatus.Removed) { + {{ 'moderation.submissionReview.withdrawn' | translate }} + } + } + {{ action.dateCreated | timeAgo }} + {{ 'moderation.submissionReview.by' | translate }} + {{ action.createdBy }} +

+ +
+ @for (attribute of attributes; track attribute.key) { + @if (!$first) { + | + } +

+ {{ attribute.label }}: {{ attribute.value }} +

+ } +
+ + @if (action.comment) { +

- {{ action.comment }}

+ } +
+} diff --git a/src/app/features/moderation/components/submission-item/submission-item.component.ts b/src/app/features/moderation/components/submission-item/submission-item.component.ts index adbdf2608..3cef5fd10 100644 --- a/src/app/features/moderation/components/submission-item/submission-item.component.ts +++ b/src/app/features/moderation/components/submission-item/submission-item.component.ts @@ -1,21 +1,80 @@ -import { TranslatePipe } from '@ngx-translate/core'; +import { createDispatchMap, select } from '@ngxs/store'; -import { ChangeDetectionStrategy, Component, input } from '@angular/core'; +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { Button } from 'primeng/button'; +import { DialogService } from 'primeng/dynamicdialog'; + +import { ChangeDetectionStrategy, Component, computed, DestroyRef, inject, input } from '@angular/core'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { ActivatedRoute, Router } from '@angular/router'; + +import { collectionFilterNames } from '@osf/features/collections/constants'; +import { SubmissionReviewStatus } from '@osf/features/moderation/enums'; import { IconComponent } from '@osf/shared/components'; +import { CollectionSubmission } from '@shared/models'; +import { TimeAgoPipe } from '@shared/pipes/time-ago.pipe'; +import { IS_XSMALL } from '@shared/utils'; import { ReviewStatusIcon } from '../../constants'; -import { Submission } from '../../models'; +import { CollectionsModerationSelectors, SetCurrentReviewAction } from '../../store/collections-moderation'; @Component({ selector: 'osf-submission-item', - imports: [TranslatePipe, IconComponent], + imports: [TranslatePipe, IconComponent, TimeAgoPipe, Button], templateUrl: './submission-item.component.html', styleUrl: './submission-item.component.scss', + providers: [DialogService], changeDetection: ChangeDetectionStrategy.OnPush, }) export class SubmissionItemComponent { - submission = input.required(); + private router = inject(Router); + private activatedRoute = inject(ActivatedRoute); + protected dialogService = inject(DialogService); + protected destroyRef = inject(DestroyRef); + protected translateService = inject(TranslateService); + protected isMobile = toSignal(inject(IS_XSMALL)); + protected reviewActions = select(CollectionsModerationSelectors.getReviewActions); + submission = input.required(); + + protected readonly reviewStatusIcon = ReviewStatusIcon; + + protected currentReviewAction = computed(() => { + const nodeId = this.submission().nodeId; + const actions = this.reviewActions(); + + if (!actions.length || !nodeId) return null; + + return actions.flat().filter((action) => action.targetNodeId === nodeId)[0]; + }); + + protected actions = createDispatchMap({ + setCurrentReviewAction: SetCurrentReviewAction, + }); + + protected currentSubmissionAttributes = computed(() => { + const item = this.submission(); + if (!item) return null; + + return collectionFilterNames + .map((attribute) => ({ + ...attribute, + value: item[attribute.key as keyof CollectionSubmission] as string, + })) + .filter((attribute) => attribute.value); + }); + + protected handleNavigation() { + this.actions.setCurrentReviewAction(this.currentReviewAction()); + + const currentStatus = this.activatedRoute.snapshot.queryParams['status']; + const queryParams = currentStatus ? { status: currentStatus } : {}; + + this.router.navigate(['../', this.submission().nodeId], { + relativeTo: this.activatedRoute, + queryParams, + }); + } - readonly reviewStatusIcon = ReviewStatusIcon; + protected readonly SubmissionReviewStatus = SubmissionReviewStatus; } diff --git a/src/app/features/moderation/components/submissions-list/submissions-list.component.html b/src/app/features/moderation/components/submissions-list/submissions-list.component.html index e75b0b742..5e6d11a21 100644 --- a/src/app/features/moderation/components/submissions-list/submissions-list.component.html +++ b/src/app/features/moderation/components/submissions-list/submissions-list.component.html @@ -1,7 +1,13 @@ -
- @for (item of submissions(); track $index) { -
- -
- } -
+@if (submissions().length) { +
+ @for (item of submissions(); track $index) { +
+ +
+ } +
+} @else { +
+

{{ 'moderation.noSubmissions' | translate }}

+
+} diff --git a/src/app/features/moderation/components/submissions-list/submissions-list.component.ts b/src/app/features/moderation/components/submissions-list/submissions-list.component.ts index fd16a47ca..29d600ca3 100644 --- a/src/app/features/moderation/components/submissions-list/submissions-list.component.ts +++ b/src/app/features/moderation/components/submissions-list/submissions-list.component.ts @@ -1,15 +1,20 @@ -import { ChangeDetectionStrategy, Component, input } from '@angular/core'; +import { select } from '@ngxs/store'; + +import { TranslatePipe } from '@ngx-translate/core'; + +import { ChangeDetectionStrategy, Component } from '@angular/core'; + +import { CollectionsModerationSelectors } from '@osf/features/moderation/store/collections-moderation'; -import { Submission } from '../../models'; import { SubmissionItemComponent } from '../submission-item/submission-item.component'; @Component({ selector: 'osf-submissions-list', - imports: [SubmissionItemComponent], + imports: [SubmissionItemComponent, TranslatePipe], templateUrl: './submissions-list.component.html', styleUrl: './submissions-list.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) export class SubmissionsListComponent { - submissions = input.required(); + submissions = select(CollectionsModerationSelectors.getCollectionSubmissions); } diff --git a/src/app/features/moderation/constants/submission.const.ts b/src/app/features/moderation/constants/submission.const.ts index 0bdf6f298..7063974b3 100644 --- a/src/app/features/moderation/constants/submission.const.ts +++ b/src/app/features/moderation/constants/submission.const.ts @@ -17,7 +17,7 @@ export const SUBMISSION_REVIEW_OPTIONS = [ label: 'moderation.submissionReviewStatus.rejected', }, { - value: SubmissionReviewStatus.Withdrawn, + value: SubmissionReviewStatus.Removed, icon: 'fas fa-circle-minus', label: 'moderation.submissionReviewStatus.withdrawn', }, @@ -63,6 +63,10 @@ export const ReviewStatusIcon: Record; - reviewActions: AsyncStateWithTotalCount; + collectionSubmissions: AsyncStateWithTotalCount; + reviewActions: AsyncStateModel; + currentSubmission: CollectionSubmission | null; + currentReviewAction: CollectionSubmissionReviewAction | null; } export const COLLECTIONS_MODERATION_STATE_DEFAULTS: CollectionsModerationStateModel = { @@ -11,11 +13,14 @@ export const COLLECTIONS_MODERATION_STATE_DEFAULTS: CollectionsModerationStateMo data: [], isLoading: false, error: null, + totalCount: 0, }, reviewActions: { data: [], isLoading: false, + isSubmitting: false, error: null, - totalCount: 0, }, + currentSubmission: null, + currentReviewAction: null, }; diff --git a/src/app/features/moderation/store/collections-moderation/collections-moderation.selectors.ts b/src/app/features/moderation/store/collections-moderation/collections-moderation.selectors.ts index 185e04df0..6eeb2f0e8 100644 --- a/src/app/features/moderation/store/collections-moderation/collections-moderation.selectors.ts +++ b/src/app/features/moderation/store/collections-moderation/collections-moderation.selectors.ts @@ -8,4 +8,39 @@ export class CollectionsModerationSelectors { static getCollectionSubmissions(state: CollectionsModerationStateModel) { return state.collectionSubmissions.data; } + + @Selector([CollectionsModerationState]) + static getCollectionSubmissionsTotalCount(state: CollectionsModerationStateModel) { + return state.collectionSubmissions.totalCount; + } + + @Selector([CollectionsModerationState]) + static getCollectionSubmissionsLoading(state: CollectionsModerationStateModel) { + return state.collectionSubmissions.isLoading; + } + + @Selector([CollectionsModerationState]) + static getReviewActions(state: CollectionsModerationStateModel) { + return state.reviewActions.data; + } + + @Selector([CollectionsModerationState]) + static getReviewActionsLoading(state: CollectionsModerationStateModel) { + return state.reviewActions.isLoading; + } + + @Selector([CollectionsModerationState]) + static getReviewActionsSubmitting(state: CollectionsModerationStateModel) { + return state.reviewActions.isSubmitting; + } + + @Selector([CollectionsModerationState]) + static getCurrentSubmission(state: CollectionsModerationStateModel) { + return state.currentSubmission; + } + + @Selector([CollectionsModerationState]) + static getCurrentReviewAction(state: CollectionsModerationStateModel) { + return state.currentReviewAction; + } } diff --git a/src/app/features/moderation/store/collections-moderation/collections-moderation.state.ts b/src/app/features/moderation/store/collections-moderation/collections-moderation.state.ts index 07efff5b8..9c26de654 100644 --- a/src/app/features/moderation/store/collections-moderation/collections-moderation.state.ts +++ b/src/app/features/moderation/store/collections-moderation/collections-moderation.state.ts @@ -6,9 +6,15 @@ import { inject, Injectable } from '@angular/core'; import { handleSectionError } from '@core/handlers'; import { CollectionsService } from '@shared/services'; -import { CollectionsStateModel } from '@shared/stores'; -import { GetCollectionSubmissions } from './collections-moderation.actions'; +import { + ClearCollectionModeration, + CreateCollectionSubmissionAction, + GetCollectionSubmissions, + GetSubmissionsReviewActions, + SetCurrentReviewAction, + SetCurrentSubmission, +} from './collections-moderation.actions'; import { COLLECTIONS_MODERATION_STATE_DEFAULTS, CollectionsModerationStateModel } from './collections-moderation.model'; @State({ @@ -20,7 +26,7 @@ export class CollectionsModerationState { collectionsService = inject(CollectionsService); @Action(GetCollectionSubmissions) - getCollectionSubmissions(ctx: StateContext, action: GetCollectionSubmissions) { + getCollectionSubmissions(ctx: StateContext, action: GetCollectionSubmissions) { const state = ctx.getState(); ctx.patchState({ collectionSubmissions: { @@ -30,18 +36,88 @@ export class CollectionsModerationState { }); return this.collectionsService - .fetchCollectionSubmissions(action.collectionId, action.status, action.page, action.sortBy) + .fetchCollectionSubmissionsByStatus(action.collectionId, action.status, action.page, action.sortBy) .pipe( tap((res) => { ctx.patchState({ collectionSubmissions: { - data: res, + data: res.data, isLoading: false, error: null, + totalCount: res.totalCount, }, }); }), catchError((error) => handleSectionError(ctx, 'collectionSubmissions', error)) ); } + + @Action(GetSubmissionsReviewActions) + getSubmissionsReviewActions(ctx: StateContext, action: GetSubmissionsReviewActions) { + ctx.patchState({ + reviewActions: { + ...ctx.getState().reviewActions, + isLoading: true, + }, + }); + + return this.collectionsService.fetchCollectionSubmissionsActions(action.submissionId, action.collectionId).pipe( + tap((res) => { + const currentState = ctx.getState(); + ctx.patchState({ + reviewActions: { + data: [...currentState.reviewActions.data, [...res]], + isLoading: false, + error: null, + }, + }); + }), + catchError((error) => handleSectionError(ctx, 'reviewActions', error)) + ); + } + + @Action(CreateCollectionSubmissionAction) + createCollectionSubmissionAction( + ctx: StateContext, + action: CreateCollectionSubmissionAction + ) { + const state = ctx.getState(); + ctx.patchState({ + reviewActions: { + ...state.reviewActions, + isSubmitting: true, + }, + }); + + return this.collectionsService.createCollectionSubmissionAction(action.payload).pipe( + tap(() => { + ctx.patchState({ + reviewActions: { + ...state.reviewActions, + isSubmitting: false, + }, + }); + }), + catchError((error) => handleSectionError(ctx, 'reviewActions', error)) + ); + } + + @Action(SetCurrentSubmission) + setCurrentSubmission(ctx: StateContext, action: SetCurrentSubmission) { + ctx.patchState({ + currentSubmission: action.submission, + }); + } + + @Action(SetCurrentReviewAction) + setCurrentReviewAction(ctx: StateContext, action: SetCurrentReviewAction) { + ctx.patchState({ + currentReviewAction: action.action, + }); + } + + @Action(ClearCollectionModeration) + clearCollectionModeration(ctx: StateContext) { + ctx.setState(COLLECTIONS_MODERATION_STATE_DEFAULTS); + } } diff --git a/src/app/features/project/overview/components/linked-projects/linked-projects.component.html b/src/app/features/project/overview/components/linked-projects/linked-projects.component.html index 89b51d278..672819560 100644 --- a/src/app/features/project/overview/components/linked-projects/linked-projects.component.html +++ b/src/app/features/project/overview/components/linked-projects/linked-projects.component.html @@ -1,7 +1,9 @@

{{ 'project.overview.linkedProjects.title' | translate }}

- + @if (!isCollectionsRoute()) { + + }
@if (isLinkedProjectsLoading()) { diff --git a/src/app/features/project/overview/components/linked-projects/linked-projects.component.ts b/src/app/features/project/overview/components/linked-projects/linked-projects.component.ts index dd3196bb8..08aa5ac58 100644 --- a/src/app/features/project/overview/components/linked-projects/linked-projects.component.ts +++ b/src/app/features/project/overview/components/linked-projects/linked-projects.component.ts @@ -7,7 +7,7 @@ import { Menu } from 'primeng/menu'; import { Skeleton } from 'primeng/skeleton'; import { NgClass } from '@angular/common'; -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, input } from '@angular/core'; import { TruncatedTextComponent } from '@osf/shared/components'; @@ -23,4 +23,5 @@ import { ProjectOverviewSelectors } from '../../store'; export class LinkedProjectsComponent { protected linkedProjects = select(ProjectOverviewSelectors.getLinkedProjects); protected isLinkedProjectsLoading = select(ProjectOverviewSelectors.getLinkedProjectsLoading); + isCollectionsRoute = input(false); } diff --git a/src/app/features/project/overview/components/overview-components/overview-components.component.html b/src/app/features/project/overview/components/overview-components/overview-components.component.html index 7e59732e3..7fe4507b5 100644 --- a/src/app/features/project/overview/components/overview-components/overview-components.component.html +++ b/src/app/features/project/overview/components/overview-components/overview-components.component.html @@ -2,11 +2,13 @@

{{ 'project.overview.components.title' | translate }}

- + @if (!isCollectionsRoute()) { + + }
diff --git a/src/app/features/project/overview/components/overview-components/overview-components.component.ts b/src/app/features/project/overview/components/overview-components/overview-components.component.ts index e22259207..ed37ad138 100644 --- a/src/app/features/project/overview/components/overview-components/overview-components.component.ts +++ b/src/app/features/project/overview/components/overview-components/overview-components.component.ts @@ -8,7 +8,7 @@ import { Menu } from 'primeng/menu'; import { Skeleton } from 'primeng/skeleton'; import { NgClass } from '@angular/common'; -import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject, input } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; import { Router } from '@angular/router'; @@ -31,6 +31,7 @@ export class OverviewComponentsComponent { private dialogService = inject(DialogService); private translateService = inject(TranslateService); protected isMobile = toSignal(inject(IS_XSMALL)); + isCollectionsRoute = input(false); protected components = select(ProjectOverviewSelectors.getComponents); protected isComponentsLoading = select(ProjectOverviewSelectors.getComponentsLoading); protected readonly componentActionItems = (componentId: string) => [ diff --git a/src/app/features/project/overview/components/overview-toolbar/overview-toolbar.component.html b/src/app/features/project/overview/components/overview-toolbar/overview-toolbar.component.html index 42c7d9ab1..2bc904cf5 100644 --- a/src/app/features/project/overview/components/overview-toolbar/overview-toolbar.component.html +++ b/src/app/features/project/overview/components/overview-toolbar/overview-toolbar.component.html @@ -2,7 +2,7 @@ @if (resource) {
- @if (visibilityToggle()) { + @if (visibilityToggle() && !isCollectionsRoute()) {
@@ -18,9 +18,23 @@
} + @if (isCollectionsRoute()) { + @if (isPublic()) { +
+ +

{{ 'project.overview.header.publicProject' | translate }}

+
+ } @else { +
+ +

{{ 'project.overview.header.privateProject' | translate }}

+
+ } + } +
- @if (resource.storage) { + @if (resource.storage && !isCollectionsRoute()) {

{{ +resource.storage.storageUsage | fileSize }}

diff --git a/src/app/features/project/overview/components/overview-toolbar/overview-toolbar.component.ts b/src/app/features/project/overview/components/overview-toolbar/overview-toolbar.component.ts index a09b20184..81fdb8568 100644 --- a/src/app/features/project/overview/components/overview-toolbar/overview-toolbar.component.ts +++ b/src/app/features/project/overview/components/overview-toolbar/overview-toolbar.component.ts @@ -52,6 +52,7 @@ export class OverviewToolbarComponent { private translateService = inject(TranslateService); private toastService = inject(ToastService); protected destroyRef = inject(DestroyRef); + isCollectionsRoute = input(false); protected isPublic = signal(false); protected isBookmarked = signal(false); diff --git a/src/app/features/project/overview/project-overview.component.html b/src/app/features/project/overview/project-overview.component.html index 50413a0de..03e11ba7b 100644 --- a/src/app/features/project/overview/project-overview.component.html +++ b/src/app/features/project/overview/project-overview.component.html @@ -1,24 +1,67 @@ @let project = currentProject(); +@let status = submissionReviewStatus(); @if (!isProjectLoading() && project) {
- +
- +
-
+
+ @if (isCollectionsRoute()) { + + + + @if (status && isCollectionsRoute()) { + @switch (status) { + @case (SubmissionReviewStatus.Pending) { + + + Pending: Pending entry into {{ collection()?.title }} + + } + @case (SubmissionReviewStatus.Accepted) { + + Accepted: Accepted entry into {{ collection()?.title }} + + } + @case (SubmissionReviewStatus.Rejected) { + + Rejected: Rejected entry into {{ collection()?.title }} + + } + @case (SubmissionReviewStatus.Removed) { + + Withdrawn: Entry withdrawn from {{ collection()?.title }} + + } + } + } + } +
- - + +
- +
} @else { diff --git a/src/app/features/project/overview/project-overview.component.ts b/src/app/features/project/overview/project-overview.component.ts index b9976ce8d..4bead7ad7 100644 --- a/src/app/features/project/overview/project-overview.component.ts +++ b/src/app/features/project/overview/project-overview.component.ts @@ -1,20 +1,36 @@ import { createDispatchMap, select } from '@ngxs/store'; +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; + import { ButtonModule } from 'primeng/button'; import { DialogService } from 'primeng/dynamicdialog'; +import { Message } from 'primeng/message'; import { TagModule } from 'primeng/tag'; import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, Component, computed, DestroyRef, HostBinding, inject, OnInit } from '@angular/core'; +import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; import { FormsModule } from '@angular/forms'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; -import { LoadingSpinnerComponent, ResourceMetadataComponent, SubHeaderComponent } from '@shared/components'; +import { SubmissionReviewStatus } from '@osf/features/moderation/enums'; +import { + LoadingSpinnerComponent, + MakeDecisionDialogComponent, + ResourceMetadataComponent, + SubHeaderComponent, +} from '@shared/components'; import { ResourceType } from '@shared/enums'; import { MapProjectOverview } from '@shared/mappers/resource-overview.mappers'; -import { GetBookmarksCollectionId } from '@shared/stores'; +import { ToastService } from '@shared/services'; +import { CollectionsSelectors, GetBookmarksCollectionId } from '@shared/stores'; import { ClearCollections } from '@shared/stores/collections'; +import { IS_XSMALL } from '@shared/utils'; +import { + ClearCollectionModeration, + CollectionsModerationSelectors, +} from '../../moderation/store/collections-moderation'; import { ClearWiki, GetHomeWiki } from '../wiki/store'; import { @@ -49,6 +65,8 @@ import { RecentActivityComponent, OverviewToolbarComponent, ResourceMetadataComponent, + TranslatePipe, + Message, ], providers: [DialogService], changeDetection: ChangeDetectionStrategy.OnPush, @@ -57,7 +75,15 @@ export class ProjectOverviewComponent implements OnInit { @HostBinding('class') classes = 'flex flex-1 flex-column w-full h-full'; private route = inject(ActivatedRoute); + private router = inject(Router); private destroyRef = inject(DestroyRef); + protected readonly toastService = inject(ToastService); + protected readonly dialogService = inject(DialogService); + protected readonly translateService = inject(TranslateService); + protected isMobile = toSignal(inject(IS_XSMALL)); + protected currentReviewAction = select(CollectionsModerationSelectors.getCurrentReviewAction); + protected currentSubmission = select(CollectionsModerationSelectors.getCurrentSubmission); + protected collection = select(CollectionsSelectors.getCollectionDetails); protected actions = createDispatchMap({ getProject: GetProjectById, @@ -68,6 +94,23 @@ export class ProjectOverviewComponent implements OnInit { clearProjectOverview: ClearProjectOverview, clearWiki: ClearWiki, clearCollections: ClearCollections, + clearCollectionModeration: ClearCollectionModeration, + }); + + readonly isCollectionsRoute = computed(() => { + return this.router.url.includes('/collections'); + }); + + submissionReviewStatus = computed(() => { + return this.currentReviewAction()?.toState; + }); + + protected showDecisionButton = computed(() => { + return ( + this.isCollectionsRoute() && + this.submissionReviewStatus() !== SubmissionReviewStatus.Removed && + this.submissionReviewStatus() !== SubmissionReviewStatus.Rejected + ); }); protected currentProject = select(ProjectOverviewSelectors.getProject); @@ -98,7 +141,7 @@ export class ProjectOverviewComponent implements OnInit { } ngOnInit(): void { - const projectId = this.route.parent?.snapshot.params['id']; + const projectId = this.route.snapshot.params['id'] || this.route.parent?.snapshot.params['id']; if (projectId) { this.actions.getProject(projectId); this.actions.getBookmarksId(); @@ -108,11 +151,46 @@ export class ProjectOverviewComponent implements OnInit { } } + handleOpenMakeDecisionDialog() { + const dialogWidth = this.isMobile() ? '95vw' : '600px'; + + this.dialogService + .open(MakeDecisionDialogComponent, { + width: dialogWidth, + focusOnShow: false, + header: this.translateService.instant('moderation.makeDecision.header'), + closeOnEscape: true, + modal: true, + closable: true, + data: { + submission: this.currentSubmission(), + reviewAction: this.currentReviewAction(), + }, + }) + .onClose.pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((data) => { + this.toastService.showSuccess(`moderation.makeDecision.${data.action}Success`); + this.goBack(); + }); + } + + goBack(): void { + const currentStatus = this.route.snapshot.queryParams['status']; + const queryParams = currentStatus ? { status: currentStatus } : {}; + this.router.navigate(['../'], { + relativeTo: this.route, + queryParams, + }); + } + private setupCleanup(): void { this.destroyRef.onDestroy(() => { this.actions.clearProjectOverview(); this.actions.clearWiki(); this.actions.clearCollections(); + this.actions.clearCollectionModeration(); }); } + + protected readonly SubmissionReviewStatus = SubmissionReviewStatus; } diff --git a/src/app/features/project/project.routes.ts b/src/app/features/project/project.routes.ts index 94cbef903..6e347a9c1 100644 --- a/src/app/features/project/project.routes.ts +++ b/src/app/features/project/project.routes.ts @@ -2,8 +2,9 @@ import { provideStates } from '@ngxs/store'; import { Routes } from '@angular/router'; +import { CollectionsModerationState } from '@osf/features/moderation/store/collections-moderation'; import { ResourceType } from '@osf/shared/enums'; -import { ContributorsState, SubjectsState, ViewOnlyLinkState } from '@osf/shared/stores'; +import { CollectionsState, ContributorsState, SubjectsState, ViewOnlyLinkState } from '@osf/shared/stores'; import { AnalyticsState } from './analytics/store'; import { ProjectFilesState } from './files/store'; @@ -23,6 +24,7 @@ export const projectRoutes: Routes = [ path: 'overview', loadComponent: () => import('../project/overview/project-overview.component').then((mod) => mod.ProjectOverviewComponent), + providers: [provideStates([CollectionsState, CollectionsModerationState])], }, { path: 'metadata', diff --git a/src/app/shared/components/index.ts b/src/app/shared/components/index.ts index 0ece2d97d..ec2cfddab 100644 --- a/src/app/shared/components/index.ts +++ b/src/app/shared/components/index.ts @@ -19,6 +19,7 @@ export { LicenseComponent } from './license/license.component'; export { LineChartComponent } from './line-chart/line-chart.component'; export { ListInfoShortenerComponent } from './list-info-shortener/list-info-shortener.component'; export { LoadingSpinnerComponent } from './loading-spinner/loading-spinner.component'; +export { MakeDecisionDialogComponent } from './make-decision-dialog/make-decision-dialog.component'; export { MarkdownComponent } from './markdown/markdown.component'; export { MyProjectsTableComponent } from './my-projects-table/my-projects-table.component'; export { PasswordInputHintComponent } from './password-input-hint/password-input-hint.component'; diff --git a/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.html b/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.html new file mode 100644 index 000000000..adf94d30c --- /dev/null +++ b/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.html @@ -0,0 +1,93 @@ +@let action = currentReviewAction(); + +@if (submissionData) { +
+ @if (action) { +

+ @switch (action.toState) { + @case (SubmissionReviewStatus.Pending) { + {{ 'moderation.submissionReview.submitted' | translate }} + } + @case (SubmissionReviewStatus.Accepted) { + {{ 'moderation.submissionReview.accepted' | translate }} + } + } + {{ action.dateCreated | timeAgo }} + {{ 'moderation.submissionReview.by' | translate }} + {{ action.createdBy }} +

+ } + +
+ @if (isPendingStatus()) { +
+ + +
+ } + + @if (isPreModeration() || (isHybridModeration() && isPendingStatus())) { +
+ + +
+ } + + @if ((isHybridModeration() && !isPendingStatus()) || isPostModeration()) { +
+ + +
+ } + +
+ +
+ +
+ + +
+
+
+} diff --git a/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.scss b/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.spec.ts b/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.spec.ts new file mode 100644 index 000000000..69fa7c0fc --- /dev/null +++ b/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MakeDecisionDialogComponent } from './make-decision-dialog.component'; + +describe('MakeDecisionDialogComponent', () => { + let component: MakeDecisionDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MakeDecisionDialogComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(MakeDecisionDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.ts b/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.ts new file mode 100644 index 000000000..cc36b4b73 --- /dev/null +++ b/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.ts @@ -0,0 +1,94 @@ +import { createDispatchMap, select } from '@ngxs/store'; + +import { TranslatePipe } from '@ngx-translate/core'; + +import { Button } from 'primeng/button'; +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { RadioButton } from 'primeng/radiobutton'; +import { Textarea } from 'primeng/textarea'; + +import { ChangeDetectionStrategy, Component, computed, inject, OnInit } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; + +import { ModerationType, SubmissionReviewStatus } from '@osf/features/moderation/enums'; +import { + CollectionsModerationSelectors, + CreateCollectionSubmissionAction, +} from '@osf/features/moderation/store/collections-moderation'; +import { ModerationDecisionFormControls, ModerationSubmitType } from '@shared/enums'; +import { TimeAgoPipe } from '@shared/pipes/time-ago.pipe'; +import { CollectionsSelectors } from '@shared/stores'; + +@Component({ + selector: 'osf-make-decision-dialog', + imports: [Button, TranslatePipe, TimeAgoPipe, FormsModule, RadioButton, ReactiveFormsModule, Textarea], + templateUrl: './make-decision-dialog.component.html', + styleUrl: './make-decision-dialog.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class MakeDecisionDialogComponent implements OnInit { + private readonly fb = inject(FormBuilder); + protected readonly config = inject(DynamicDialogConfig); + protected readonly dialogRef = inject(DynamicDialogRef); + protected readonly ModerationSubmitType = ModerationSubmitType; + protected readonly SubmissionReviewStatus = SubmissionReviewStatus; + protected readonly ModerationDecisionFormControls = ModerationDecisionFormControls; + protected collectionProvider = select(CollectionsSelectors.getCollectionProvider); + protected currentReviewAction = select(CollectionsModerationSelectors.getCurrentReviewAction); + protected isSubmitting = select(CollectionsModerationSelectors.getReviewActionsSubmitting); + protected submissionData = this.config.data; + protected requestForm!: FormGroup; + + protected actions = createDispatchMap({ + createSubmissionAction: CreateCollectionSubmissionAction, + }); + + protected isHybridModeration = computed(() => { + const provider = this.collectionProvider(); + return provider?.reviewsWorkflow === ModerationType.Hybrid || !provider?.reviewsWorkflow; + }); + + protected isPreModeration = computed(() => { + const provider = this.collectionProvider(); + return provider?.reviewsWorkflow === ModerationType.Pre; + }); + + protected isPostModeration = computed(() => { + const provider = this.collectionProvider(); + return provider?.reviewsWorkflow === ModerationType.Post; + }); + + protected isPendingStatus = computed(() => { + return this.currentReviewAction()?.toState === SubmissionReviewStatus.Pending; + }); + + ngOnInit() { + this.initForm(); + } + + protected handleSubmission(): void { + const targetId = this.submissionData.reviewAction.targetId; + if (this.requestForm.valid && targetId) { + const formData = this.requestForm.value; + const payload = { ...formData, targetId }; + + this.actions.createSubmissionAction(payload).subscribe({ + next: () => { + this.dialogRef.close(formData); + }, + }); + } + } + + get isSubmitDisabled(): boolean { + const actionControl = this.requestForm?.get(ModerationDecisionFormControls.Action); + return !actionControl?.value || this.isSubmitting()!; + } + + private initForm(): void { + this.requestForm = this.fb.group({ + [ModerationDecisionFormControls.Action]: new FormControl('', [Validators.required]), + [ModerationDecisionFormControls.Comment]: new FormControl(''), + }); + } +} diff --git a/src/app/shared/components/resource-metadata/resource-metadata.component.html b/src/app/shared/components/resource-metadata/resource-metadata.component.html index 9a6a93e23..e2165f753 100644 --- a/src/app/shared/components/resource-metadata/resource-metadata.component.html +++ b/src/app/shared/components/resource-metadata/resource-metadata.component.html @@ -5,11 +5,13 @@
diff --git a/src/app/shared/components/resource-metadata/resource-metadata.component.ts b/src/app/shared/components/resource-metadata/resource-metadata.component.ts index 6b02fa95f..6f9a16cb8 100644 --- a/src/app/shared/components/resource-metadata/resource-metadata.component.ts +++ b/src/app/shared/components/resource-metadata/resource-metadata.component.ts @@ -20,6 +20,7 @@ import { ResourceOverview } from '@shared/models'; }) export class ResourceMetadataComponent { currentResource = input.required(); + isCollectionsRoute = input(false); protected readonly resourceTypes = OsfResourceTypes; } 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 78a577e25..cd96e260a 100644 --- a/src/app/shared/components/sub-header/sub-header.component.html +++ b/src/app/shared/components/sub-header/sub-header.component.html @@ -23,7 +23,13 @@

@if (showButton()) {
- +
} 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 3e2fb91b0..3546ab69d 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,4 @@ -import { Button } from 'primeng/button'; +import { Button, ButtonSeverity } from 'primeng/button'; import { SafeHtmlPipe } from 'primeng/menu'; import { Skeleton } from 'primeng/skeleton'; import { Tooltip } from 'primeng/tooltip'; @@ -15,6 +15,7 @@ import { ChangeDetectionStrategy, Component, input, output } from '@angular/core export class SubHeaderComponent { showButton = input(false); buttonLabel = input(''); + buttonSeverity = input('primary'); title = input(''); icon = input(''); tooltip = input(''); diff --git a/src/app/shared/constants/sort-options.const.ts b/src/app/shared/constants/sort-options.const.ts index 915f9ff75..c09f67b71 100644 --- a/src/app/shared/constants/sort-options.const.ts +++ b/src/app/shared/constants/sort-options.const.ts @@ -19,3 +19,22 @@ export const ALL_SORT_OPTIONS: CustomOption[] = [ label: 'project.files.sort.lastModifiedNewest', }, ]; + +export const COLLECTION_SUBMISSIONS_SORT_OPTIONS: CustomOption[] = [ + { + value: SortType.LastModifiedNewest, + label: 'project.files.sort.lastModifiedNewest', + }, + { + value: SortType.LastModifiedOldest, + label: 'project.files.sort.lastModifiedOldest', + }, + { + value: SortType.TitleAZ, + label: 'project.files.sort.nameAZ', + }, + { + value: SortType.TitleZA, + label: 'project.files.sort.nameZA', + }, +]; diff --git a/src/app/shared/enums/index.ts b/src/app/shared/enums/index.ts index 64dbb2988..fa368e3b4 100644 --- a/src/app/shared/enums/index.ts +++ b/src/app/shared/enums/index.ts @@ -9,6 +9,8 @@ export * from './create-project-form-controls.enum'; export * from './file-menu-type.enum'; export * from './filter-type.enum'; export * from './get-resources-request-type.enum'; +export * from './moderation-decision-form-controls.enum'; +export * from './moderation-submit-type.enum'; export * from './profile-addons-stepper.enum'; export * from './registration-review-states.enum'; export * from './registry-status.enum'; diff --git a/src/app/shared/enums/moderation-decision-form-controls.enum.ts b/src/app/shared/enums/moderation-decision-form-controls.enum.ts new file mode 100644 index 000000000..437c33de1 --- /dev/null +++ b/src/app/shared/enums/moderation-decision-form-controls.enum.ts @@ -0,0 +1,4 @@ +export enum ModerationDecisionFormControls { + 'Action' = 'action', + 'Comment' = 'comment', +} diff --git a/src/app/shared/enums/moderation-submit-type.enum.ts b/src/app/shared/enums/moderation-submit-type.enum.ts new file mode 100644 index 000000000..3e84faa5a --- /dev/null +++ b/src/app/shared/enums/moderation-submit-type.enum.ts @@ -0,0 +1,5 @@ +export enum ModerationSubmitType { + 'Accept' = 'accept', + 'Reject' = 'reject', + 'Withdraw' = 'remove', +} diff --git a/src/app/shared/enums/sort-type.enum.ts b/src/app/shared/enums/sort-type.enum.ts index e45dcd466..f1bce0ce3 100644 --- a/src/app/shared/enums/sort-type.enum.ts +++ b/src/app/shared/enums/sort-type.enum.ts @@ -3,4 +3,6 @@ export enum SortType { NameZA = '-name', LastModifiedOldest = 'date_modified', LastModifiedNewest = '-date_modified', + TitleAZ = 'title', + TitleZA = '-title', } diff --git a/src/app/shared/mappers/collections/collections.mapper.ts b/src/app/shared/mappers/collections/collections.mapper.ts index eccd8b034..2b55caf99 100644 --- a/src/app/shared/mappers/collections/collections.mapper.ts +++ b/src/app/shared/mappers/collections/collections.mapper.ts @@ -1,3 +1,8 @@ +import { JsonApiResponseWithPaging } from '@core/models'; +import { + CollectionSubmissionReviewAction, + CollectionSubmissionReviewActionJsonApi, +} from '@osf/features/moderation/models'; import { CollectionContributor, CollectionContributorJsonApi, @@ -9,6 +14,9 @@ import { CollectionSubmissionJsonApi, CollectionSubmissionPayload, CollectionSubmissionPayloadJsonApi, + PaginatedData, + ReviewActionPayload, + ReviewActionPayloadJsonApi, } from '@osf/shared/models'; import { convertToSnakeCase } from '@shared/utils'; @@ -78,29 +86,58 @@ export class CollectionsMapper { }; } - static fromGetCollectionSubmissionsResponse(response: CollectionSubmissionJsonApi[]): CollectionSubmission[] { - return response.map((submission) => ({ - id: submission.id, - type: submission.type, - nodeId: submission.embeds.guid.data.id, - nodeUrl: submission.embeds.guid.data.links.html, - title: submission.embeds.guid.data.attributes.title, - description: submission.embeds.guid.data.attributes.description, - category: submission.embeds.guid.data.attributes.category, - dateCreated: submission.embeds.guid.data.attributes.date_created, - dateModified: submission.embeds.guid.data.attributes.date_modified, - public: submission.embeds.guid.data.attributes.public, - reviewsState: submission.attributes.reviews_state, - collectedType: submission.attributes.collected_type, - status: submission.attributes.status, - volume: submission.attributes.volume, - issue: submission.attributes.issue, - programArea: submission.attributes.program_area, - schoolType: submission.attributes.school_type, - studyDesign: submission.attributes.study_design, - dataType: submission.attributes.data_type, - disease: submission.attributes.disease, - gradeLevels: submission.attributes.grade_levels, + static fromGetCollectionSubmissionsResponse( + response: JsonApiResponseWithPaging + ): PaginatedData { + return { + data: response.data.map((submission) => ({ + id: submission.id, + type: submission.type, + nodeId: submission.embeds.guid.data.id, + nodeUrl: submission.embeds.guid.data.links.html, + title: submission.embeds.guid.data.attributes.title, + description: submission.embeds.guid.data.attributes.description, + category: submission.embeds.guid.data.attributes.category, + dateCreated: submission.embeds.guid.data.attributes.date_created, + dateModified: submission.embeds.guid.data.attributes.date_modified, + public: submission.embeds.guid.data.attributes.public, + reviewsState: submission.attributes.reviews_state, + collectedType: submission.attributes.collected_type, + status: submission.attributes.status, + volume: submission.attributes.volume, + issue: submission.attributes.issue, + programArea: submission.attributes.program_area, + schoolType: submission.attributes.school_type, + studyDesign: submission.attributes.study_design, + dataType: submission.attributes.data_type, + disease: submission.attributes.disease, + gradeLevels: submission.attributes.grade_levels, + creator: submission.embeds.creator + ? { + id: submission.embeds.creator.data.id, + fullName: submission.embeds.creator.data.attributes.full_name, + } + : undefined, + })), + totalCount: response.links.meta.total, + }; + } + + static fromGetCollectionSubmissionsActionsResponse( + response: CollectionSubmissionReviewActionJsonApi[] + ): CollectionSubmissionReviewAction[] { + return response.map((action) => ({ + id: action.id, + type: action.type, + dateModified: action.attributes.date_modified, + dateCreated: action.attributes.date_created, + fromState: action.attributes.from_state, + toState: action.attributes.to_state, + trigger: action.attributes.trigger, + comment: action.attributes.comment, + targetId: action.relationships.target.data.id, + targetNodeId: action.relationships.target.data.id.split('-')[0], + createdBy: action.embeds.creator.data.attributes.full_name, })); } @@ -159,4 +196,24 @@ export class CollectionsMapper { }, }; } + + static toReviewActionPayloadJsonApi(payload: ReviewActionPayload): ReviewActionPayloadJsonApi { + return { + data: { + type: 'collection_submission_actions', + attributes: { + trigger: payload.action, + comment: payload.comment, + }, + relationships: { + target: { + data: { + type: 'collection-submissions', + id: payload.targetId, + }, + }, + }, + }, + }; + } } diff --git a/src/app/shared/models/collections/collections-json-api.models.ts b/src/app/shared/models/collections/collections-json-api.models.ts index 0f713998f..dff8a6826 100644 --- a/src/app/shared/models/collections/collections-json-api.models.ts +++ b/src/app/shared/models/collections/collections-json-api.models.ts @@ -102,6 +102,19 @@ export interface CollectionSubmissionJsonApi { }; }; }; + creator?: { + data: { + attributes: { + full_name: string; + }; + id: string; + }; + }; + }; + links: { + meta: { + total: number; + }; }; } diff --git a/src/app/shared/models/collections/collections.models.ts b/src/app/shared/models/collections/collections.models.ts index 7c76cb833..b78cb14fa 100644 --- a/src/app/shared/models/collections/collections.models.ts +++ b/src/app/shared/models/collections/collections.models.ts @@ -82,4 +82,8 @@ export interface CollectionSubmission { disease: string; gradeLevels: string; contributors?: CollectionContributor[]; + creator?: { + id: string; + fullName: string; + }; } diff --git a/src/app/shared/models/collections/index.ts b/src/app/shared/models/collections/index.ts index 7f8a3362e..64017c688 100644 --- a/src/app/shared/models/collections/index.ts +++ b/src/app/shared/models/collections/index.ts @@ -3,3 +3,5 @@ export * from './collection-submission-payload-json-api.model'; export * from './collections.models'; export * from './collections-filters.model'; export * from './collections-json-api.models'; +export * from './review-action-payload.model'; +export * from './review-action-payload-json-api.model'; diff --git a/src/app/shared/models/collections/review-action-payload-json-api.model.ts b/src/app/shared/models/collections/review-action-payload-json-api.model.ts new file mode 100644 index 000000000..a46314e84 --- /dev/null +++ b/src/app/shared/models/collections/review-action-payload-json-api.model.ts @@ -0,0 +1,17 @@ +export interface ReviewActionPayloadJsonApi { + data: { + type: 'collection_submission_actions'; + attributes: { + trigger: string; + comment: string; + }; + relationships: { + target: { + data: { + type: 'collection-submissions'; + id: string; + }; + }; + }; + }; +} diff --git a/src/app/shared/models/collections/review-action-payload.model.ts b/src/app/shared/models/collections/review-action-payload.model.ts new file mode 100644 index 000000000..52951c24a --- /dev/null +++ b/src/app/shared/models/collections/review-action-payload.model.ts @@ -0,0 +1,5 @@ +export interface ReviewActionPayload { + targetId: string; + action: string; + comment: string; +} diff --git a/src/app/shared/pipes/time-ago.pipe.ts b/src/app/shared/pipes/time-ago.pipe.ts new file mode 100644 index 000000000..f1195b17e --- /dev/null +++ b/src/app/shared/pipes/time-ago.pipe.ts @@ -0,0 +1,41 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'timeAgo', +}) +export class TimeAgoPipe implements PipeTransform { + transform(value: Date | string): string { + if (!value) { + return ''; + } + + const date = new Date(value); + const now = new Date(); + const seconds = Math.floor((now.getTime() - date.getTime()) / 1000); + + const minute = 60; + const hour = minute * 60; + const day = hour * 24; + const month = day * 30; + const year = day * 365; + + if (seconds < minute) { + return seconds === 0 ? 'just now' : `${seconds} seconds ago`; + } else if (seconds < hour) { + const minutes = Math.floor(seconds / minute); + return `${minutes} minute${minutes > 1 ? 's' : ''} ago`; + } else if (seconds < day) { + const hours = Math.floor(seconds / hour); + return `${hours} hour${hours > 1 ? 's' : ''} ago`; + } else if (seconds < month) { + const days = Math.floor(seconds / day); + return `${days} day${days > 1 ? 's' : ''} ago`; + } else if (seconds < year) { + const months = Math.floor(seconds / month); + return `${months} month${months > 1 ? 's' : ''} ago`; + } else { + const years = Math.floor(seconds / year); + return `${years} year${years > 1 ? 's' : ''} ago`; + } + } +} diff --git a/src/app/shared/services/collections.service.ts b/src/app/shared/services/collections.service.ts index 6696c7aa8..0fc59e059 100644 --- a/src/app/shared/services/collections.service.ts +++ b/src/app/shared/services/collections.service.ts @@ -6,6 +6,10 @@ import { inject, Injectable } from '@angular/core'; import { JsonApiResponse, JsonApiResponseWithPaging } from '@core/models'; import { JsonApiService } from '@core/services'; +import { + CollectionSubmissionReviewAction, + CollectionSubmissionReviewActionJsonApi, +} from '@osf/features/moderation/models'; import { CollectionsMapper } from '@shared/mappers/collections'; import { CollectionContributor, @@ -17,7 +21,10 @@ import { CollectionSubmissionJsonApi, CollectionSubmissionsSearchPayloadJsonApi, ContributorsResponseJsonApi, + PaginatedData, + ReviewActionPayload, } from '@shared/models'; +import { ReviewActionPayloadJsonApi } from '@shared/models/collections/review-action-payload-json-api.model'; import { SetTotalSubmissions } from '@shared/stores/collections'; import { environment } from 'src/environments/environment'; @@ -101,36 +108,61 @@ export class CollectionsService { ); } - fetchCollectionSubmissions( + fetchCollectionSubmissionsByStatus( collectionId: string, status: string, page = '1', sortBy: string - ): Observable { + ): Observable> { const params: Record = { page, 'filter[reviews_state]': status, 'page[size]': '10', + embed: 'creator', sort: sortBy, }; return this.jsonApiService .get< - JsonApiResponse + JsonApiResponseWithPaging >(`${environment.apiUrl}/collections/${collectionId}/collection_submissions/`, params) .pipe( map((response) => { - return CollectionsMapper.fromGetCollectionSubmissionsResponse(response.data); + return CollectionsMapper.fromGetCollectionSubmissionsResponse(response); }) ); } + fetchCollectionSubmissionsActions( + projectId: string, + collectionId: string + ): Observable { + const params: Record = { + embed: 'creator', + }; + + return this.jsonApiService + .get< + JsonApiResponse + >(`${environment.apiUrl}/collection_submissions/${projectId}-${collectionId}/actions/?sort=-date_modified`, params) + .pipe(map((response) => CollectionsMapper.fromGetCollectionSubmissionsActionsResponse(response.data))); + } + fetchAllUserCollectionSubmissions(providerId: string, projectIds: string[]): Observable { const pendingSubmissions$ = this.fetchUserCollectionSubmissionsByStatus(providerId, projectIds, 'pending'); const acceptedSubmissions$ = this.fetchUserCollectionSubmissionsByStatus(providerId, projectIds, 'accepted'); return forkJoin([pendingSubmissions$, acceptedSubmissions$]).pipe( - map(([pending, accepted]) => [...pending, ...accepted]) + map(([pending, accepted]) => [...pending.data, ...accepted.data]) + ); + } + + createCollectionSubmissionAction(payload: ReviewActionPayload): Observable { + const params = CollectionsMapper.toReviewActionPayloadJsonApi(payload); + + return this.jsonApiService.post( + `${environment.apiUrl}/collection_submission_actions/`, + params ); } @@ -150,7 +182,7 @@ export class CollectionsService { providerId: string, projectIds: string[], submissionStatus: string - ): Observable { + ): Observable> { const params: Record = { 'filter[reviews_state]': submissionStatus, 'filter[id]': projectIds.join(','), @@ -158,11 +190,11 @@ export class CollectionsService { return this.jsonApiService .get< - JsonApiResponse + JsonApiResponseWithPaging >(`${environment.apiUrl}/collections/${providerId}/collection_submissions/`, params) .pipe( map((response) => { - return CollectionsMapper.fromGetCollectionSubmissionsResponse(response.data); + return CollectionsMapper.fromGetCollectionSubmissionsResponse(response); }) ); } diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 56a20cb47..f0d7081a4 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -1107,6 +1107,7 @@ "inviteModerator": "Invite moderator", "searchModerator": "Search moderator", "selectPermission": "Select permission", + "noSubmissions": "No submissions", "moderatorPermissions": { "administrator": "Administrator", "moderator": "Moderator" @@ -1129,10 +1130,27 @@ "public": "Public", "embargo": "Embargo" }, + "makeDecision": { + "header": "Make decision", + "requestSubmitted": "Request submitted", + "acceptRequest": "Accept request", + "rejectRequest": "Reject request", + "withdrawRequest": "Withdraw Request", + "acceptRequestMessage": "Submission will appear in search results and will be associated with the collection.", + "rejectRequestMessage": "Submission will not appear in search results nor will be associated with the collection.", + "withdrawRequestMessage": "Submission will be withdrawn from the collection and will no longer appear in search results.", + "remarksPlaceholder": "Add remarks to project admins (optional)", + "submitDecision": "Submit decision", + "goBackLink": "< Back to list of submissions", + "acceptSuccess": "Submission has been accepted successfully", + "rejectSuccess": "Submission has been rejected successfully", + "removeSuccess": "Submission has been withdrawn successfully" + }, "submissionReview": { "submitted": "Submitted", "accepted": "Accepted", "rejected": "Rejected", + "withdrawn": "Withdrawn", "by": "by" }, "preprintReviewStatus": { diff --git a/src/assets/styles/overrides/button.scss b/src/assets/styles/overrides/button.scss index cbd3f8fa1..f94ef6803 100644 --- a/src/assets/styles/overrides/button.scss +++ b/src/assets/styles/overrides/button.scss @@ -50,3 +50,17 @@ --p-button-text-danger-color: var(--red-3); } } + +.submission-link-btn { + .p-button { + --p-button-padding-x: 0.5625rem; + --p-button-padding-y: 0; + } +} + +.back-link-btn { + .p-button { + --p-button-padding-y: 0; + --p-button-padding-x: 0; + } +} diff --git a/src/assets/styles/overrides/message.scss b/src/assets/styles/overrides/message.scss index 36619a575..ddbe2b92d 100644 --- a/src/assets/styles/overrides/message.scss +++ b/src/assets/styles/overrides/message.scss @@ -23,3 +23,9 @@ .p-message-simple { --p-message-text-font-weight: 400; } + +.overview-message { + .p-message { + width: 100%; + } +} From ffc2365a20dcf1cf37be49595218d2123ceb8266 Mon Sep 17 00:00:00 2001 From: Roman Nastyuk Date: Wed, 30 Jul 2025 15:04:57 +0300 Subject: [PATCH 3/7] feat(collection-moderation): fixed bugs after merge --- src/app/features/collections/collections.routes.ts | 4 ++-- .../components/linked-resources/linked-resources.component.ts | 3 ++- .../features/project/overview/project-overview.component.html | 2 +- .../features/project/overview/project-overview.component.ts | 1 - 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/features/collections/collections.routes.ts b/src/app/features/collections/collections.routes.ts index db9c2a9d3..d16253660 100644 --- a/src/app/features/collections/collections.routes.ts +++ b/src/app/features/collections/collections.routes.ts @@ -5,7 +5,7 @@ import { Routes } from '@angular/router'; import { AddToCollectionState } from '@osf/features/collections/store/add-to-collection'; import { CollectionsModerationState } from '@osf/features/moderation/store/collections-moderation'; import { ConfirmLeavingGuard } from '@shared/guards'; -import { BookmarksState, ContributorsState, ProjectsState } from '@shared/stores'; +import { BookmarksState, ContributorsState, NodeLinksState, ProjectsState } from '@shared/stores'; import { CollectionsState } from '@shared/stores/collections'; export const collectionsRoutes: Routes = [ @@ -53,7 +53,7 @@ export const collectionsRoutes: Routes = [ import( '@osf/features/moderation/components/collection-submission-overview/collection-submission-overview.component' ).then((mod) => mod.CollectionSubmissionOverviewComponent), - providers: [provideStates([CollectionsModerationState, CollectionsState, BookmarksState])], + providers: [provideStates([NodeLinksState, CollectionsModerationState, CollectionsState, BookmarksState])], }, ], }, diff --git a/src/app/features/project/overview/components/linked-resources/linked-resources.component.ts b/src/app/features/project/overview/components/linked-resources/linked-resources.component.ts index 6e44c70e0..adbac7eae 100644 --- a/src/app/features/project/overview/components/linked-resources/linked-resources.component.ts +++ b/src/app/features/project/overview/components/linked-resources/linked-resources.component.ts @@ -7,7 +7,7 @@ import { DialogService } from 'primeng/dynamicdialog'; import { Skeleton } from 'primeng/skeleton'; import { NgClass } from '@angular/common'; -import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject, input } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; import { DeleteNodeLinkDialogComponent, LinkResourceDialogComponent } from '@osf/features/project/overview/components'; @@ -26,6 +26,7 @@ import { IS_XSMALL } from '@shared/utils'; export class LinkedResourcesComponent { private dialogService = inject(DialogService); private translateService = inject(TranslateService); + isCollectionsRoute = input(false); protected linkedResources = select(NodeLinksSelectors.getLinkedResources); protected isLinkedResourcesLoading = select(NodeLinksSelectors.getLinkedResourcesLoading); protected isMobile = toSignal(inject(IS_XSMALL)); diff --git a/src/app/features/project/overview/project-overview.component.html b/src/app/features/project/overview/project-overview.component.html index 03e11ba7b..7fc7eee42 100644 --- a/src/app/features/project/overview/project-overview.component.html +++ b/src/app/features/project/overview/project-overview.component.html @@ -56,7 +56,7 @@
- +
diff --git a/src/app/features/project/overview/project-overview.component.ts b/src/app/features/project/overview/project-overview.component.ts index 20f1a2713..53f2a2656 100644 --- a/src/app/features/project/overview/project-overview.component.ts +++ b/src/app/features/project/overview/project-overview.component.ts @@ -13,7 +13,6 @@ import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; import { FormsModule } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; -import { ClearCollections } from '@osf/features/collections/store/collections'; import { SubmissionReviewStatus } from '@osf/features/moderation/enums'; import { LoadingSpinnerComponent, From 5b061af696dd04cab1f1f30ac2b54f8568485bf2 Mon Sep 17 00:00:00 2001 From: Roman Nastyuk Date: Wed, 30 Jul 2025 15:17:51 +0300 Subject: [PATCH 4/7] feat(collection-moderation): fixed loading state inconsistency --- ...llection-moderation-submissions.component.html | 2 +- ...collection-moderation-submissions.component.ts | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.html b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.html index ceb8eba7f..b12aa877f 100644 --- a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.html +++ b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.html @@ -26,7 +26,7 @@

-@if (!isSubmissionsLoading() && !isReviewActionsLoading()) { +@if (!isLoading()) { @if (collectionSubmissions().length) { diff --git a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts index 3723be9bf..9bf204e90 100644 --- a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts +++ b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts @@ -59,15 +59,26 @@ export class CollectionModerationSubmissionsComponent { protected collectionProvider = select(CollectionsSelectors.getCollectionProvider); protected collectionDetails = select(CollectionsSelectors.getCollectionDetails); - protected collectionSubmissions = select(CollectionsModerationSelectors.getCollectionSubmissions); - protected totalSubmissions = select(CollectionsModerationSelectors.getCollectionSubmissionsTotalCount); + protected isCollectionProviderLoading = select(CollectionsSelectors.getCollectionProviderLoading); + protected isCollectionDetailsLoading = select(CollectionsSelectors.getCollectionDetailsLoading); protected isSubmissionsLoading = select(CollectionsModerationSelectors.getCollectionSubmissionsLoading); protected isReviewActionsLoading = select(CollectionsModerationSelectors.getReviewActionsLoading); + protected collectionSubmissions = select(CollectionsModerationSelectors.getCollectionSubmissions); + protected totalSubmissions = select(CollectionsModerationSelectors.getCollectionSubmissionsTotalCount); protected providerId = signal(''); protected primaryCollectionId = computed(() => this.collectionProvider()?.primaryCollection?.id); protected reviewStatus = signal(SubmissionReviewStatus.Pending); protected currentPage = signal('1'); + protected isLoading = computed(() => { + return ( + this.isCollectionProviderLoading() || + this.isCollectionDetailsLoading() || + this.isSubmissionsLoading() || + this.isReviewActionsLoading() + ); + }); + sortOptions = COLLECTION_SUBMISSIONS_SORT_OPTIONS; selectedSortOption = signal(this.sortOptions[0].value); From c8d1903e652b8fcbf15fd7119452a1eac9e236ff Mon Sep 17 00:00:00 2001 From: Roman Nastyuk Date: Thu, 31 Jul 2025 12:49:33 +0300 Subject: [PATCH 5/7] feat(collection moderation): fixed comments --- src/app/core/interceptors/auth.interceptor.ts | 2 +- .../collections/collections.routes.ts | 6 +- ...tion-moderation-submissions.component.html | 4 +- ...ection-moderation-submissions.component.ts | 16 +-- .../submission-item.component.html | 9 +- .../submission-item.component.ts | 10 +- .../collection-moderation.component.ts | 2 - .../overview/project-overview.component.html | 2 +- .../overview/project-overview.component.ts | 6 +- .../make-decision-dialog.component.html | 1 + .../resource-citations.component.html | 125 +++++++++--------- .../resource-citations.component.ts | 1 + .../resource-metadata.component.html | 1 + src/app/shared/services/node-links.service.ts | 2 +- src/assets/styles/overrides/button.scss | 9 +- 15 files changed, 96 insertions(+), 100 deletions(-) diff --git a/src/app/core/interceptors/auth.interceptor.ts b/src/app/core/interceptors/auth.interceptor.ts index aaaee6079..8b5e55217 100644 --- a/src/app/core/interceptors/auth.interceptor.ts +++ b/src/app/core/interceptors/auth.interceptor.ts @@ -6,7 +6,7 @@ export const authInterceptor: HttpInterceptorFn = ( req: HttpRequest, next: HttpHandlerFn ): Observable> => { - const authToken = '2rjFZwmdDG4rtKj7hGkEMO6XyHBM2lN7XBbsA1e8OqcFhOWu6Z7fQZiheu9RXtzSeVrgOt'; + const authToken = 'UlO9O9GNKgVzJD7pUeY53jiQTKJ4U2znXVWNvh0KZQruoENuILx0IIYf9LoDz7Duq72EIm'; // UlO9O9GNKgVzJD7pUeY53jiQTKJ4U2znXVWNvh0KZQruoENuILx0IIYf9LoDz7Duq72EIm kyrylo // 2rjFZwmdDG4rtKj7hGkEMO6XyHBM2lN7XBbsA1e8OqcFhOWu6Z7fQZiheu9RXtzSeVrgOt roman nastyuk // yZ485nN6MfhqvGrfU4Xk5BEnq0T6LM50nQ6H9VrYaMTaZUQNTuxnIwlp0Wpz879RCsK9GQ NM stage3 diff --git a/src/app/features/collections/collections.routes.ts b/src/app/features/collections/collections.routes.ts index d16253660..b34f680cc 100644 --- a/src/app/features/collections/collections.routes.ts +++ b/src/app/features/collections/collections.routes.ts @@ -5,7 +5,7 @@ import { Routes } from '@angular/router'; import { AddToCollectionState } from '@osf/features/collections/store/add-to-collection'; import { CollectionsModerationState } from '@osf/features/moderation/store/collections-moderation'; import { ConfirmLeavingGuard } from '@shared/guards'; -import { BookmarksState, ContributorsState, NodeLinksState, ProjectsState } from '@shared/stores'; +import { BookmarksState, CitationsState, ContributorsState, NodeLinksState, ProjectsState } from '@shared/stores'; import { CollectionsState } from '@shared/stores/collections'; export const collectionsRoutes: Routes = [ @@ -53,7 +53,9 @@ export const collectionsRoutes: Routes = [ import( '@osf/features/moderation/components/collection-submission-overview/collection-submission-overview.component' ).then((mod) => mod.CollectionSubmissionOverviewComponent), - providers: [provideStates([NodeLinksState, CollectionsModerationState, CollectionsState, BookmarksState])], + providers: [ + provideStates([NodeLinksState, CitationsState, CollectionsModerationState, CollectionsState, BookmarksState]), + ], }, ], }, diff --git a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.html b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.html index b12aa877f..f3f43114b 100644 --- a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.html +++ b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.html @@ -29,11 +29,11 @@ @if (!isLoading()) { - @if (collectionSubmissions().length) { + @if (collectionSubmissions().length > pageSize) { diff --git a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts index 9bf204e90..ca05e87f5 100644 --- a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts +++ b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts @@ -58,9 +58,7 @@ export class CollectionModerationSubmissionsComponent { readonly submissionReviewOptions = SUBMISSION_REVIEW_OPTIONS; protected collectionProvider = select(CollectionsSelectors.getCollectionProvider); - protected collectionDetails = select(CollectionsSelectors.getCollectionDetails); protected isCollectionProviderLoading = select(CollectionsSelectors.getCollectionProviderLoading); - protected isCollectionDetailsLoading = select(CollectionsSelectors.getCollectionDetailsLoading); protected isSubmissionsLoading = select(CollectionsModerationSelectors.getCollectionSubmissionsLoading); protected isReviewActionsLoading = select(CollectionsModerationSelectors.getReviewActionsLoading); protected collectionSubmissions = select(CollectionsModerationSelectors.getCollectionSubmissions); @@ -69,14 +67,10 @@ export class CollectionModerationSubmissionsComponent { protected primaryCollectionId = computed(() => this.collectionProvider()?.primaryCollection?.id); protected reviewStatus = signal(SubmissionReviewStatus.Pending); protected currentPage = signal('1'); + protected pageSize = 10; protected isLoading = computed(() => { - return ( - this.isCollectionProviderLoading() || - this.isCollectionDetailsLoading() || - this.isSubmissionsLoading() || - this.isReviewActionsLoading() - ); + return this.isCollectionProviderLoading() || this.isSubmissionsLoading() || this.isReviewActionsLoading(); }); sortOptions = COLLECTION_SUBMISSIONS_SORT_OPTIONS; @@ -104,13 +98,13 @@ export class CollectionModerationSubmissionsComponent { }); effect(() => { - const collectionDetails = this.collectionDetails(); + const provider = this.collectionProvider(); const status = this.reviewStatus(); const sortBy = this.selectedSortOption() || this.sortOptions[0].value; const page = this.currentPage(); - if (collectionDetails && status) { - this.actions.getCollectionSubmissions(collectionDetails.id, status, page, sortBy); + if (status && provider) { + this.actions.getCollectionSubmissions(provider.primaryCollection.id, status, page, sortBy); } }); diff --git a/src/app/features/moderation/components/submission-item/submission-item.component.html b/src/app/features/moderation/components/submission-item/submission-item.component.html index edf5702ba..23dc5e3d0 100644 --- a/src/app/features/moderation/components/submission-item/submission-item.component.html +++ b/src/app/features/moderation/components/submission-item/submission-item.component.html @@ -9,8 +9,13 @@ [iconClass]="reviewStatusIcon[submission().reviewsState].icon" > - -

{{ submission().title }}

+
diff --git a/src/app/features/moderation/components/submission-item/submission-item.component.ts b/src/app/features/moderation/components/submission-item/submission-item.component.ts index 3cef5fd10..7cc5d20a1 100644 --- a/src/app/features/moderation/components/submission-item/submission-item.component.ts +++ b/src/app/features/moderation/components/submission-item/submission-item.component.ts @@ -1,12 +1,11 @@ import { createDispatchMap, select } from '@ngxs/store'; -import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { TranslatePipe } from '@ngx-translate/core'; import { Button } from 'primeng/button'; import { DialogService } from 'primeng/dynamicdialog'; -import { ChangeDetectionStrategy, Component, computed, DestroyRef, inject, input } from '@angular/core'; -import { toSignal } from '@angular/core/rxjs-interop'; +import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { collectionFilterNames } from '@osf/features/collections/constants'; @@ -14,7 +13,6 @@ import { SubmissionReviewStatus } from '@osf/features/moderation/enums'; import { IconComponent } from '@osf/shared/components'; import { CollectionSubmission } from '@shared/models'; import { TimeAgoPipe } from '@shared/pipes/time-ago.pipe'; -import { IS_XSMALL } from '@shared/utils'; import { ReviewStatusIcon } from '../../constants'; import { CollectionsModerationSelectors, SetCurrentReviewAction } from '../../store/collections-moderation'; @@ -30,10 +28,6 @@ import { CollectionsModerationSelectors, SetCurrentReviewAction } from '../../st export class SubmissionItemComponent { private router = inject(Router); private activatedRoute = inject(ActivatedRoute); - protected dialogService = inject(DialogService); - protected destroyRef = inject(DestroyRef); - protected translateService = inject(TranslateService); - protected isMobile = toSignal(inject(IS_XSMALL)); protected reviewActions = select(CollectionsModerationSelectors.getReviewActions); submission = input.required(); diff --git a/src/app/features/moderation/pages/collection-moderation/collection-moderation.component.ts b/src/app/features/moderation/pages/collection-moderation/collection-moderation.component.ts index 99846ce20..c397a903a 100644 --- a/src/app/features/moderation/pages/collection-moderation/collection-moderation.component.ts +++ b/src/app/features/moderation/pages/collection-moderation/collection-moderation.component.ts @@ -9,7 +9,6 @@ import { ActivatedRoute, Router, RouterOutlet } from '@angular/router'; import { Primitive } from '@osf/core/helpers'; import { SelectComponent, SubHeaderComponent } from '@osf/shared/components'; -import { ResourceType } from '@osf/shared/enums'; import { IS_MEDIUM } from '@osf/shared/utils'; import { COLLECTION_MODERATION_TABS } from '../../constants'; @@ -33,7 +32,6 @@ import { CollectionModerationTab } from '../../enums'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class CollectionModerationComponent implements OnInit { - readonly resourceType = ResourceType.Collection; readonly route = inject(ActivatedRoute); readonly router = inject(Router); diff --git a/src/app/features/project/overview/project-overview.component.html b/src/app/features/project/overview/project-overview.component.html index c1cdeb0f7..d91e8e5b7 100644 --- a/src/app/features/project/overview/project-overview.component.html +++ b/src/app/features/project/overview/project-overview.component.html @@ -21,7 +21,7 @@ diff --git a/src/app/features/project/overview/project-overview.component.ts b/src/app/features/project/overview/project-overview.component.ts index 13cd9a25b..ab44dc4b1 100644 --- a/src/app/features/project/overview/project-overview.component.ts +++ b/src/app/features/project/overview/project-overview.component.ts @@ -176,8 +176,10 @@ export class ProjectOverviewComponent implements OnInit { }) .onClose.pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((data) => { - this.toastService.showSuccess(`moderation.makeDecision.${data.action}Success`); - this.goBack(); + if (data && data.action) { + this.toastService.showSuccess(`moderation.makeDecision.${data.action}Success`); + this.goBack(); + } }); } diff --git a/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.html b/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.html index adf94d30c..e5906424a 100644 --- a/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.html +++ b/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.html @@ -75,6 +75,7 @@ {{ citation.title }} }
} - -

{{ 'project.overview.metadata.getMoreCitations' | translate }}

- - - {{ selectedOption.label }} - - - @if (styledCitation()) { -

{{ styledCitation()?.citation }}

+ @if (!isCollectionsRoute()) { + +

{{ 'project.overview.metadata.getMoreCitations' | translate }}

+ + + {{ selectedOption.label }} + + + @if (styledCitation()) { +

{{ styledCitation()?.citation }}

+ } + + + } - + } + } + } @else { + @if (!isCollectionsRoute()) { + +
- } + > + + + + + +
} - } @else { - -
- - - - - - -
} diff --git a/src/app/shared/components/resource-citations/resource-citations.component.ts b/src/app/shared/components/resource-citations/resource-citations.component.ts index dc7cabd9b..0a140f8a7 100644 --- a/src/app/shared/components/resource-citations/resource-citations.component.ts +++ b/src/app/shared/components/resource-citations/resource-citations.component.ts @@ -57,6 +57,7 @@ import { export class ResourceCitationsComponent { private readonly destroyRef = inject(DestroyRef); private readonly translateService = inject(TranslateService); + isCollectionsRoute = input(false); currentResource = input.required(); private readonly clipboard = inject(Clipboard); private readonly toastService = inject(ToastService); diff --git a/src/app/shared/components/resource-metadata/resource-metadata.component.html b/src/app/shared/components/resource-metadata/resource-metadata.component.html index f90d15ee4..bfc09ba59 100644 --- a/src/app/shared/components/resource-metadata/resource-metadata.component.html +++ b/src/app/shared/components/resource-metadata/resource-metadata.component.html @@ -169,6 +169,7 @@

{{ 'project.overview.metadata.tags' | translate }}

} diff --git a/src/app/shared/services/node-links.service.ts b/src/app/shared/services/node-links.service.ts index cf834b8d9..4b3a0cdf8 100644 --- a/src/app/shared/services/node-links.service.ts +++ b/src/app/shared/services/node-links.service.ts @@ -71,7 +71,7 @@ export class NodeLinksService { return this.jsonApiService .get< JsonApiResponse - >(`${environment.apiUrl}/nodes/${projectId}/linked_nodes`, params) + >(`${environment.apiUrl}/nodes/${projectId}/linked_nodes/`, params) .pipe( map((response) => { return response.data.map((item) => ComponentsMapper.fromGetComponentResponse(item)); diff --git a/src/assets/styles/overrides/button.scss b/src/assets/styles/overrides/button.scss index 32b6ca65c..5231609c7 100644 --- a/src/assets/styles/overrides/button.scss +++ b/src/assets/styles/overrides/button.scss @@ -65,14 +65,7 @@ } } -.submission-link-btn { - .p-button { - --p-button-padding-x: 0.5625rem; - --p-button-padding-y: 0; - } -} - -.back-link-btn { +.link-btn-no-padding { .p-button { --p-button-padding-y: 0; --p-button-padding-x: 0; From bbda7f75c49dfc5ce0d70bc9a81ab3b3e4e821af Mon Sep 17 00:00:00 2001 From: Roman Nastyuk Date: Thu, 31 Jul 2025 16:19:59 +0300 Subject: [PATCH 6/7] feat(collection-moderation): changed collection-moderation store structure --- ...ection-moderation-submissions.component.ts | 3 +- .../collection-submission-item.component.html | 6 +- .../collection-submission-item.component.ts | 21 ++---- ...ollection-submission-overview.component.ts | 32 ++------- .../collections-moderation.actions.ts | 15 +--- .../collections-moderation.model.ts | 11 +-- .../collections-moderation.selectors.ts | 22 ++---- .../collections-moderation.state.ts | 69 +++++++++---------- .../overview/project-overview.component.html | 12 ++-- .../overview/project-overview.component.ts | 62 ++++++++++++++--- .../features/registries/registries.routes.ts | 4 +- .../mappers/registry-overview.mapper.ts | 1 + .../get-registry-overview-json-api.model.ts | 1 + .../make-decision-dialog.component.html | 28 ++++---- .../make-decision-dialog.component.ts | 6 +- .../models/collections/collections.models.ts | 3 + 16 files changed, 143 insertions(+), 153 deletions(-) diff --git a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts index c26d0bf9b..73b4ee5db 100644 --- a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts +++ b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts @@ -60,7 +60,6 @@ export class CollectionModerationSubmissionsComponent { protected collectionProvider = select(CollectionsSelectors.getCollectionProvider); protected isCollectionProviderLoading = select(CollectionsSelectors.getCollectionProviderLoading); protected isSubmissionsLoading = select(CollectionsModerationSelectors.getCollectionSubmissionsLoading); - protected isReviewActionsLoading = select(CollectionsModerationSelectors.getReviewActionsLoading); protected collectionSubmissions = select(CollectionsModerationSelectors.getCollectionSubmissions); protected totalSubmissions = select(CollectionsModerationSelectors.getCollectionSubmissionsTotalCount); protected providerId = signal(''); @@ -70,7 +69,7 @@ export class CollectionModerationSubmissionsComponent { protected pageSize = 10; protected isLoading = computed(() => { - return this.isCollectionProviderLoading() || this.isSubmissionsLoading() || this.isReviewActionsLoading(); + return this.isCollectionProviderLoading() || this.isSubmissionsLoading(); }); sortOptions = COLLECTION_SUBMISSIONS_SORT_OPTIONS; diff --git a/src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.html b/src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.html index 23dc5e3d0..ed06d8552 100644 --- a/src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.html +++ b/src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.html @@ -25,7 +25,11 @@ {{ 'moderation.submissionReview.submitted' | translate }} } @case (SubmissionReviewStatus.Accepted) { - {{ 'moderation.submissionReview.accepted' | translate }} + @if (!collectionProvider()?.reviewsWorkflow) { + {{ 'moderation.submissionReview.submitted' | translate }} + } @else { + {{ 'moderation.submissionReview.accepted' | translate }} + } } @case (SubmissionReviewStatus.Rejected) { {{ 'moderation.submissionReview.rejected' | translate }} diff --git a/src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.ts b/src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.ts index 86847a9e1..8ccb6af20 100644 --- a/src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.ts +++ b/src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.ts @@ -1,4 +1,4 @@ -import { createDispatchMap, select } from '@ngxs/store'; +import { select } from '@ngxs/store'; import { TranslatePipe } from '@ngx-translate/core'; @@ -13,9 +13,9 @@ import { SubmissionReviewStatus } from '@osf/features/moderation/enums'; import { IconComponent } from '@osf/shared/components'; import { CollectionSubmission } from '@shared/models'; import { TimeAgoPipe } from '@shared/pipes/time-ago.pipe'; +import { CollectionsSelectors } from '@shared/stores'; import { ReviewStatusIcon } from '../../constants'; -import { CollectionsModerationSelectors, SetCurrentReviewAction } from '../../store/collections-moderation'; @Component({ selector: 'osf-submission-item', @@ -28,22 +28,17 @@ import { CollectionsModerationSelectors, SetCurrentReviewAction } from '../../st export class CollectionSubmissionItemComponent { private router = inject(Router); private activatedRoute = inject(ActivatedRoute); - protected reviewActions = select(CollectionsModerationSelectors.getReviewActions); submission = input.required(); + collectionProvider = select(CollectionsSelectors.getCollectionProvider); protected readonly reviewStatusIcon = ReviewStatusIcon; protected currentReviewAction = computed(() => { - const nodeId = this.submission().nodeId; - const actions = this.reviewActions(); + const actions = this.submission().actions; - if (!actions.length || !nodeId) return null; + if (!actions || !actions.length) return null; - return actions.flat().filter((action) => action.targetNodeId === nodeId)[0]; - }); - - protected actions = createDispatchMap({ - setCurrentReviewAction: SetCurrentReviewAction, + return actions[0]; }); protected currentSubmissionAttributes = computed(() => { @@ -59,10 +54,8 @@ export class CollectionSubmissionItemComponent { }); protected handleNavigation() { - this.actions.setCurrentReviewAction(this.currentReviewAction()); - const currentStatus = this.activatedRoute.snapshot.queryParams['status']; - const queryParams = currentStatus ? { status: currentStatus } : {}; + const queryParams = currentStatus ? { status: currentStatus, mode: 'moderation' } : {}; this.router.navigate(['../', this.submission().nodeId], { relativeTo: this.activatedRoute, diff --git a/src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.ts b/src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.ts index ab7c50033..ab7707460 100644 --- a/src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.ts +++ b/src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.ts @@ -1,12 +1,7 @@ -import { createDispatchMap, select } from '@ngxs/store'; - -import { ChangeDetectionStrategy, Component, effect, inject } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, effect, inject } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { ProjectOverviewComponent } from '@osf/features/project/overview/project-overview.component'; -import { CollectionSubmission } from '@shared/models'; - -import { CollectionsModerationSelectors, SetCurrentSubmission } from '../../store/collections-moderation'; @Component({ selector: 'osf-collection-submission-overview', @@ -19,33 +14,18 @@ export class CollectionSubmissionOverviewComponent { private route = inject(ActivatedRoute); private router = inject(Router); private activatedRoute = inject(ActivatedRoute); - protected submissions = select(CollectionsModerationSelectors.getCollectionSubmissions); - protected actions = createDispatchMap({ - setCurrentSubmission: SetCurrentSubmission, + readonly isModerationMode = computed(() => { + const mode = this.route.snapshot.queryParams['mode']; + + return mode === 'moderation'; }); constructor() { effect(() => { - const submissions = this.submissions(); - if (!submissions.length) { + if (!this.isModerationMode()) { this.router.navigate(['../'], { relativeTo: this.activatedRoute }); } }); - - effect(() => { - const submissionId = this.route.snapshot.params['id']; - const submissions = this.submissions(); - - if (!submissionId || !submissions) return; - - const currentSubmission = submissions.find( - (submission: CollectionSubmission) => submission.nodeId === submissionId - ); - - if (currentSubmission) { - this.actions.setCurrentSubmission(currentSubmission); - } - }); } } diff --git a/src/app/features/moderation/store/collections-moderation/collections-moderation.actions.ts b/src/app/features/moderation/store/collections-moderation/collections-moderation.actions.ts index d28f27bec..47feaeb77 100644 --- a/src/app/features/moderation/store/collections-moderation/collections-moderation.actions.ts +++ b/src/app/features/moderation/store/collections-moderation/collections-moderation.actions.ts @@ -1,5 +1,4 @@ -import { CollectionSubmissionReviewAction } from '@osf/features/moderation/models'; -import { CollectionSubmission, ReviewActionPayload } from '@shared/models'; +import { ReviewActionPayload } from '@shared/models'; export class GetCollectionSubmissions { static readonly type = '[Collections Moderation] Get Collection Submissions'; @@ -27,18 +26,6 @@ export class CreateCollectionSubmissionAction { constructor(public payload: ReviewActionPayload) {} } -export class SetCurrentSubmission { - static readonly type = '[Collections Moderation] Set Current Submission'; - - constructor(public submission: CollectionSubmission | null) {} -} - -export class SetCurrentReviewAction { - static readonly type = '[Collections Moderation] Set Current Review Action'; - - constructor(public action: CollectionSubmissionReviewAction | null) {} -} - export class ClearCollectionModeration { static readonly type = '[Collections Moderation] ClearCollectionModeration'; } diff --git a/src/app/features/moderation/store/collections-moderation/collections-moderation.model.ts b/src/app/features/moderation/store/collections-moderation/collections-moderation.model.ts index ab30b97f9..6f07d7dc2 100644 --- a/src/app/features/moderation/store/collections-moderation/collections-moderation.model.ts +++ b/src/app/features/moderation/store/collections-moderation/collections-moderation.model.ts @@ -3,9 +3,7 @@ import { AsyncStateModel, AsyncStateWithTotalCount, CollectionSubmission } from export interface CollectionsModerationStateModel { collectionSubmissions: AsyncStateWithTotalCount; - reviewActions: AsyncStateModel; - currentSubmission: CollectionSubmission | null; - currentReviewAction: CollectionSubmissionReviewAction | null; + currentReviewAction: AsyncStateModel; } export const COLLECTIONS_MODERATION_STATE_DEFAULTS: CollectionsModerationStateModel = { @@ -15,12 +13,9 @@ export const COLLECTIONS_MODERATION_STATE_DEFAULTS: CollectionsModerationStateMo error: null, totalCount: 0, }, - reviewActions: { - data: [], + currentReviewAction: { + data: null, isLoading: false, - isSubmitting: false, error: null, }, - currentSubmission: null, - currentReviewAction: null, }; diff --git a/src/app/features/moderation/store/collections-moderation/collections-moderation.selectors.ts b/src/app/features/moderation/store/collections-moderation/collections-moderation.selectors.ts index 6eeb2f0e8..01c7560a6 100644 --- a/src/app/features/moderation/store/collections-moderation/collections-moderation.selectors.ts +++ b/src/app/features/moderation/store/collections-moderation/collections-moderation.selectors.ts @@ -20,27 +20,17 @@ export class CollectionsModerationSelectors { } @Selector([CollectionsModerationState]) - static getReviewActions(state: CollectionsModerationStateModel) { - return state.reviewActions.data; + static getCollectionSubmissionSubmitting(state: CollectionsModerationStateModel) { + return state.collectionSubmissions.isSubmitting; } @Selector([CollectionsModerationState]) - static getReviewActionsLoading(state: CollectionsModerationStateModel) { - return state.reviewActions.isLoading; - } - - @Selector([CollectionsModerationState]) - static getReviewActionsSubmitting(state: CollectionsModerationStateModel) { - return state.reviewActions.isSubmitting; - } - - @Selector([CollectionsModerationState]) - static getCurrentSubmission(state: CollectionsModerationStateModel) { - return state.currentSubmission; + static getCurrentReviewAction(state: CollectionsModerationStateModel) { + return state.currentReviewAction.data; } @Selector([CollectionsModerationState]) - static getCurrentReviewAction(state: CollectionsModerationStateModel) { - return state.currentReviewAction; + static getCurrentReviewActionLoading(state: CollectionsModerationStateModel) { + return state.currentReviewAction.isLoading; } } diff --git a/src/app/features/moderation/store/collections-moderation/collections-moderation.state.ts b/src/app/features/moderation/store/collections-moderation/collections-moderation.state.ts index 9c26de654..a9b57c184 100644 --- a/src/app/features/moderation/store/collections-moderation/collections-moderation.state.ts +++ b/src/app/features/moderation/store/collections-moderation/collections-moderation.state.ts @@ -1,6 +1,7 @@ import { Action, State, StateContext } from '@ngxs/store'; +import { patch } from '@ngxs/store/operators'; -import { catchError, tap } from 'rxjs'; +import { catchError, forkJoin, map, of, switchMap, tap } from 'rxjs'; import { inject, Injectable } from '@angular/core'; @@ -12,8 +13,6 @@ import { CreateCollectionSubmissionAction, GetCollectionSubmissions, GetSubmissionsReviewActions, - SetCurrentReviewAction, - SetCurrentSubmission, } from './collections-moderation.actions'; import { COLLECTIONS_MODERATION_STATE_DEFAULTS, CollectionsModerationStateModel } from './collections-moderation.model'; @@ -27,17 +26,30 @@ export class CollectionsModerationState { @Action(GetCollectionSubmissions) getCollectionSubmissions(ctx: StateContext, action: GetCollectionSubmissions) { - const state = ctx.getState(); - ctx.patchState({ - collectionSubmissions: { - ...state.collectionSubmissions, - isLoading: true, - }, - }); + ctx.setState(patch({ collectionSubmissions: patch({ isLoading: true }) })); return this.collectionsService .fetchCollectionSubmissionsByStatus(action.collectionId, action.status, action.page, action.sortBy) .pipe( + switchMap((res) => { + if (!res.data.length) { + return of({ + data: [], + totalCount: res.totalCount, + }); + } + + const actionRequests = res.data.map((submission) => + this.collectionsService.fetchCollectionSubmissionsActions(submission.nodeId, action.collectionId) + ); + + return forkJoin(actionRequests).pipe( + map((actions) => ({ + data: res.data.map((submission, i) => ({ ...submission, actions: actions[i] })), + totalCount: res.totalCount, + })) + ); + }), tap((res) => { ctx.patchState({ collectionSubmissions: { @@ -53,26 +65,25 @@ export class CollectionsModerationState { } @Action(GetSubmissionsReviewActions) - getSubmissionsReviewActions(ctx: StateContext, action: GetSubmissionsReviewActions) { + getCurrentReviewAction(ctx: StateContext, action: GetSubmissionsReviewActions) { ctx.patchState({ - reviewActions: { - ...ctx.getState().reviewActions, + currentReviewAction: { + ...ctx.getState().currentReviewAction, isLoading: true, }, }); return this.collectionsService.fetchCollectionSubmissionsActions(action.submissionId, action.collectionId).pipe( tap((res) => { - const currentState = ctx.getState(); ctx.patchState({ - reviewActions: { - data: [...currentState.reviewActions.data, [...res]], + currentReviewAction: { + data: res[0] || null, isLoading: false, error: null, }, }); }), - catchError((error) => handleSectionError(ctx, 'reviewActions', error)) + catchError((error) => handleSectionError(ctx, 'currentReviewAction', error)) ); } @@ -83,8 +94,8 @@ export class CollectionsModerationState { ) { const state = ctx.getState(); ctx.patchState({ - reviewActions: { - ...state.reviewActions, + collectionSubmissions: { + ...state.collectionSubmissions, isSubmitting: true, }, }); @@ -92,30 +103,16 @@ export class CollectionsModerationState { return this.collectionsService.createCollectionSubmissionAction(action.payload).pipe( tap(() => { ctx.patchState({ - reviewActions: { - ...state.reviewActions, + collectionSubmissions: { + ...state.collectionSubmissions, isSubmitting: false, }, }); }), - catchError((error) => handleSectionError(ctx, 'reviewActions', error)) + catchError((error) => handleSectionError(ctx, 'collectionSubmissions', error)) ); } - @Action(SetCurrentSubmission) - setCurrentSubmission(ctx: StateContext, action: SetCurrentSubmission) { - ctx.patchState({ - currentSubmission: action.submission, - }); - } - - @Action(SetCurrentReviewAction) - setCurrentReviewAction(ctx: StateContext, action: SetCurrentReviewAction) { - ctx.patchState({ - currentReviewAction: action.action, - }); - } - @Action(ClearCollectionModeration) clearCollectionModeration(ctx: StateContext) { ctx.setState(COLLECTIONS_MODERATION_STATE_DEFAULTS); diff --git a/src/app/features/project/overview/project-overview.component.html b/src/app/features/project/overview/project-overview.component.html index d91e8e5b7..f83db97e8 100644 --- a/src/app/features/project/overview/project-overview.component.html +++ b/src/app/features/project/overview/project-overview.component.html @@ -1,7 +1,7 @@ @let project = currentProject(); @let status = submissionReviewStatus(); -@if (!isProjectLoading() && project) { +@if (!isLoading() && project) {
- @if (status && isCollectionsRoute()) { + @if (status && isCollectionsRoute() && collectionProvider()) { @switch (status) { @case (SubmissionReviewStatus.Pending) { - Pending: Pending entry into {{ collection()?.title }} + Pending: Pending entry into {{ collectionProvider()?.name }} } @case (SubmissionReviewStatus.Accepted) { - Accepted: Accepted entry into {{ collection()?.title }} + Accepted: Accepted entry into {{ collectionProvider()?.name }} } @case (SubmissionReviewStatus.Rejected) { - Rejected: Rejected entry into {{ collection()?.title }} + Rejected: Rejected entry into {{ collectionProvider()?.name }} } @case (SubmissionReviewStatus.Removed) { - Withdrawn: Entry withdrawn from {{ collection()?.title }} + Withdrawn: Entry withdrawn from {{ collectionProvider()?.name }} } } diff --git a/src/app/features/project/overview/project-overview.component.ts b/src/app/features/project/overview/project-overview.component.ts index 8f52b10b7..de1261f6a 100644 --- a/src/app/features/project/overview/project-overview.component.ts +++ b/src/app/features/project/overview/project-overview.component.ts @@ -8,7 +8,16 @@ import { Message } from 'primeng/message'; import { TagModule } from 'primeng/tag'; import { CommonModule } from '@angular/common'; -import { ChangeDetectionStrategy, Component, computed, DestroyRef, HostBinding, inject, OnInit } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + computed, + DestroyRef, + effect, + HostBinding, + inject, + OnInit, +} from '@angular/core'; import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; import { FormsModule } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; @@ -28,6 +37,7 @@ import { CollectionsSelectors, GetAllNodeLinks, GetBookmarksCollectionId, + GetCollectionProvider, GetHomeWiki, GetLinkedResources, } from '@shared/stores'; @@ -37,6 +47,7 @@ import { IS_XSMALL } from '@shared/utils'; import { ClearCollectionModeration, CollectionsModerationSelectors, + GetSubmissionsReviewActions, } from '../../moderation/store/collections-moderation'; import { @@ -87,9 +98,9 @@ export class ProjectOverviewComponent implements OnInit { protected readonly dialogService = inject(DialogService); protected readonly translateService = inject(TranslateService); protected isMobile = toSignal(inject(IS_XSMALL)); + protected submissions = select(CollectionsModerationSelectors.getCollectionSubmissions); + protected collectionProvider = select(CollectionsSelectors.getCollectionProvider); protected currentReviewAction = select(CollectionsModerationSelectors.getCurrentReviewAction); - protected currentSubmission = select(CollectionsModerationSelectors.getCurrentSubmission); - protected collection = select(CollectionsSelectors.getCollectionDetails); protected actions = createDispatchMap({ getProject: GetProjectById, @@ -99,6 +110,8 @@ export class ProjectOverviewComponent implements OnInit { getLinkedProjects: GetLinkedResources, getNodeLinks: GetAllNodeLinks, setProjectCustomCitation: SetProjectCustomCitation, + getCollectionProvider: GetCollectionProvider, + getCurrentReviewAction: GetSubmissionsReviewActions, clearProjectOverview: ClearProjectOverview, clearWiki: ClearWiki, clearCollections: ClearCollections, @@ -109,6 +122,12 @@ export class ProjectOverviewComponent implements OnInit { return this.router.url.includes('/collections'); }); + readonly isModerationMode = computed(() => { + const mode = this.route.snapshot.queryParams['mode']; + + return mode === 'moderation'; + }); + submissionReviewStatus = computed(() => { return this.currentReviewAction()?.toState; }); @@ -130,6 +149,11 @@ export class ProjectOverviewComponent implements OnInit { return null; }); protected isProjectLoading = select(ProjectOverviewSelectors.getProjectLoading); + protected isCollectionProviderLoading = select(CollectionsSelectors.getCollectionProviderLoading); + protected isReviewActionsLoading = select(CollectionsModerationSelectors.getCurrentReviewActionLoading); + protected isLoading = computed(() => { + return this.isProjectLoading() || this.isCollectionProviderLoading() || this.isReviewActionsLoading(); + }); protected currentResource = computed(() => { if (this.currentProject()) { return { @@ -145,10 +169,11 @@ export class ProjectOverviewComponent implements OnInit { }); constructor() { + this.setupCollectionsEffects(); this.setupCleanup(); } - onCustomCitationUpdated(citation: string): void { + protected onCustomCitationUpdated(citation: string): void { this.actions.setProjectCustomCitation(citation); } @@ -164,7 +189,7 @@ export class ProjectOverviewComponent implements OnInit { } } - handleOpenMakeDecisionDialog() { + protected handleOpenMakeDecisionDialog() { const dialogWidth = this.isMobile() ? '95vw' : '600px'; this.dialogService @@ -175,10 +200,6 @@ export class ProjectOverviewComponent implements OnInit { closeOnEscape: true, modal: true, closable: true, - data: { - submission: this.currentSubmission(), - reviewAction: this.currentReviewAction(), - }, }) .onClose.pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((data) => { @@ -189,7 +210,7 @@ export class ProjectOverviewComponent implements OnInit { }); } - goBack(): void { + protected goBack(): void { const currentStatus = this.route.snapshot.queryParams['status']; const queryParams = currentStatus ? { status: currentStatus } : {}; @@ -199,6 +220,27 @@ export class ProjectOverviewComponent implements OnInit { }); } + private setupCollectionsEffects(): void { + effect(() => { + if (this.isModerationMode() && this.isCollectionsRoute()) { + const collectionId = this.route.snapshot.params['collectionId']; + + this.actions.getCollectionProvider(collectionId); + } + }); + + effect(() => { + if (this.isModerationMode() && this.isCollectionsRoute()) { + const provider = this.collectionProvider(); + const resource = this.currentResource(); + + if (!provider || !resource) return; + + this.actions.getCurrentReviewAction(resource.id, provider.primaryCollection.id); + } + }); + } + private setupCleanup(): void { this.destroyRef.onDestroy(() => { this.actions.clearProjectOverview(); diff --git a/src/app/features/registries/registries.routes.ts b/src/app/features/registries/registries.routes.ts index 1a921441a..ace160f71 100644 --- a/src/app/features/registries/registries.routes.ts +++ b/src/app/features/registries/registries.routes.ts @@ -5,7 +5,7 @@ import { Routes } from '@angular/router'; import { RegistriesComponent } from '@osf/features/registries/registries.component'; import { RegistriesState } from '@osf/features/registries/store'; import { RegistriesProviderSearchState } from '@osf/features/registries/store/registries-provider-search'; -import { ContributorsState, SubjectsState } from '@osf/shared/stores'; +import { CitationsState, ContributorsState, SubjectsState } from '@osf/shared/stores'; import { LicensesHandlers, ProjectsHandlers, ProvidersHandlers } from './store/handlers'; import { FilesHandlers } from './store/handlers/files.handlers'; @@ -16,7 +16,7 @@ export const registriesRoutes: Routes = [ path: '', component: RegistriesComponent, providers: [ - provideStates([RegistriesState, ContributorsState, SubjectsState, RegistriesProviderSearchState]), + provideStates([RegistriesState, CitationsState, ContributorsState, SubjectsState, RegistriesProviderSearchState]), ProvidersHandlers, ProjectsHandlers, LicensesHandlers, diff --git a/src/app/features/registry/mappers/registry-overview.mapper.ts b/src/app/features/registry/mappers/registry-overview.mapper.ts index d6f417103..1a4b93bf7 100644 --- a/src/app/features/registry/mappers/registry-overview.mapper.ts +++ b/src/app/features/registry/mappers/registry-overview.mapper.ts @@ -13,6 +13,7 @@ export function MapRegistryOverview(data: RegistryOverviewJsonApiData): Registry dateCreated: data.attributes?.date_created, dateRegistered: data.attributes?.date_registered, category: data.attributes?.category, + customCitation: data.attributes?.custom_citation, isFork: data.attributes?.fork, accessRequestsEnabled: data.attributes?.accessRequestsEnabled, nodeLicense: data.attributes.node_license diff --git a/src/app/features/registry/models/get-registry-overview-json-api.model.ts b/src/app/features/registry/models/get-registry-overview-json-api.model.ts index d0ccd9e0b..36c46d544 100644 --- a/src/app/features/registry/models/get-registry-overview-json-api.model.ts +++ b/src/app/features/registry/models/get-registry-overview-json-api.model.ts @@ -23,6 +23,7 @@ export interface RegistryOverviewJsonApiAttributes { tags: string[]; category: string; fork?: boolean; + custom_citation?: string | null; accessRequestsEnabled?: boolean; node_license?: { copyright_holders: string[]; diff --git a/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.html b/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.html index e5906424a..9165bba00 100644 --- a/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.html +++ b/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.html @@ -1,22 +1,20 @@ @let action = currentReviewAction(); -@if (submissionData) { +@if (action) {
- @if (action) { -

- @switch (action.toState) { - @case (SubmissionReviewStatus.Pending) { - {{ 'moderation.submissionReview.submitted' | translate }} - } - @case (SubmissionReviewStatus.Accepted) { - {{ 'moderation.submissionReview.accepted' | translate }} - } +

+ @switch (action.toState) { + @case (SubmissionReviewStatus.Pending) { + {{ 'moderation.submissionReview.submitted' | translate }} } - {{ action.dateCreated | timeAgo }} - {{ 'moderation.submissionReview.by' | translate }} - {{ action.createdBy }} -

- } + @case (SubmissionReviewStatus.Accepted) { + {{ 'moderation.submissionReview.accepted' | translate }} + } + } + {{ action.dateCreated | timeAgo }} + {{ 'moderation.submissionReview.by' | translate }} + {{ action.createdBy }} +

@if (isPendingStatus()) { diff --git a/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.ts b/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.ts index cc36b4b73..3cc58affc 100644 --- a/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.ts +++ b/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.ts @@ -35,8 +35,8 @@ export class MakeDecisionDialogComponent implements OnInit { protected readonly ModerationDecisionFormControls = ModerationDecisionFormControls; protected collectionProvider = select(CollectionsSelectors.getCollectionProvider); protected currentReviewAction = select(CollectionsModerationSelectors.getCurrentReviewAction); - protected isSubmitting = select(CollectionsModerationSelectors.getReviewActionsSubmitting); - protected submissionData = this.config.data; + + protected isSubmitting = select(CollectionsModerationSelectors.getCollectionSubmissionSubmitting); protected requestForm!: FormGroup; protected actions = createDispatchMap({ @@ -67,7 +67,7 @@ export class MakeDecisionDialogComponent implements OnInit { } protected handleSubmission(): void { - const targetId = this.submissionData.reviewAction.targetId; + const targetId = this.currentReviewAction()?.targetId; if (this.requestForm.valid && targetId) { const formData = this.requestForm.value; const payload = { ...formData, targetId }; diff --git a/src/app/shared/models/collections/collections.models.ts b/src/app/shared/models/collections/collections.models.ts index b78cb14fa..b19412c04 100644 --- a/src/app/shared/models/collections/collections.models.ts +++ b/src/app/shared/models/collections/collections.models.ts @@ -1,3 +1,5 @@ +import { CollectionSubmissionReviewAction } from '@osf/features/moderation/models'; + export interface CollectionProvider { id: string; type: string; @@ -86,4 +88,5 @@ export interface CollectionSubmission { id: string; fullName: string; }; + actions?: CollectionSubmissionReviewAction[]; } From cf4dd6f27c6ebdceae4369bf54287585c3f819cd Mon Sep 17 00:00:00 2001 From: Roman Nastyuk Date: Thu, 31 Jul 2025 21:56:58 +0300 Subject: [PATCH 7/7] feat(collection-moderation): fixed comments --- .../collection-submission-overview.component.ts | 3 ++- .../features/project/overview/project-overview.component.ts | 4 ++-- .../make-decision-dialog/make-decision-dialog.component.html | 1 - src/app/shared/enums/index.ts | 1 + src/app/shared/enums/mode.enum.ts | 3 +++ 5 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 src/app/shared/enums/mode.enum.ts diff --git a/src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.ts b/src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.ts index ab7707460..d0a610b59 100644 --- a/src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.ts +++ b/src/app/features/moderation/components/collection-submission-overview/collection-submission-overview.component.ts @@ -2,6 +2,7 @@ import { ChangeDetectionStrategy, Component, computed, effect, inject } from '@a import { ActivatedRoute, Router } from '@angular/router'; import { ProjectOverviewComponent } from '@osf/features/project/overview/project-overview.component'; +import { Mode } from '@shared/enums'; @Component({ selector: 'osf-collection-submission-overview', @@ -18,7 +19,7 @@ export class CollectionSubmissionOverviewComponent { readonly isModerationMode = computed(() => { const mode = this.route.snapshot.queryParams['mode']; - return mode === 'moderation'; + return mode === Mode.Moderation; }); constructor() { diff --git a/src/app/features/project/overview/project-overview.component.ts b/src/app/features/project/overview/project-overview.component.ts index de1261f6a..78aa5c925 100644 --- a/src/app/features/project/overview/project-overview.component.ts +++ b/src/app/features/project/overview/project-overview.component.ts @@ -29,7 +29,7 @@ import { ResourceMetadataComponent, SubHeaderComponent, } from '@shared/components'; -import { ResourceType } from '@shared/enums'; +import { Mode, ResourceType } from '@shared/enums'; import { MapProjectOverview } from '@shared/mappers/resource-overview.mappers'; import { ToastService } from '@shared/services'; import { @@ -125,7 +125,7 @@ export class ProjectOverviewComponent implements OnInit { readonly isModerationMode = computed(() => { const mode = this.route.snapshot.queryParams['mode']; - return mode === 'moderation'; + return mode === Mode.Moderation; }); submissionReviewStatus = computed(() => { diff --git a/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.html b/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.html index 9165bba00..2dbdc2029 100644 --- a/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.html +++ b/src/app/shared/components/make-decision-dialog/make-decision-dialog.component.html @@ -73,7 +73,6 @@