Skip to content

Commit 3b726d8

Browse files
authored
Feat/Project overview collections (#219)
* feat(project-overview-collections): added collections info to project overview page * feat(project-overview-collections): fixed collection moderation bug
1 parent 7f1b725 commit 3b726d8

File tree

20 files changed

+447
-72
lines changed

20 files changed

+447
-72
lines changed

src/app/features/collections/components/collections-search-result-card/collections-search-result-card.component.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { DatePipe } from '@angular/common';
44
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
55

66
import { collectionFilterNames } from '@osf/features/collections/constants';
7-
import { CollectionSubmission } from '@shared/models';
7+
import { CollectionSubmissionWithGuid } from '@shared/models';
88

99
@Component({
1010
selector: 'osf-collections-search-result-card',
@@ -14,7 +14,7 @@ import { CollectionSubmission } from '@shared/models';
1414
changeDetection: ChangeDetectionStrategy.OnPush,
1515
})
1616
export class CollectionsSearchResultCardComponent {
17-
cardItem = input.required<CollectionSubmission>();
17+
cardItem = input.required<CollectionSubmissionWithGuid>();
1818

1919
protected presentSubmissionAttributes = computed(() => {
2020
const item = this.cardItem();
@@ -23,7 +23,7 @@ export class CollectionsSearchResultCardComponent {
2323
return collectionFilterNames
2424
.map((attribute) => ({
2525
...attribute,
26-
value: item[attribute.key as keyof CollectionSubmission] as string,
26+
value: item[attribute.key as keyof CollectionSubmissionWithGuid] as string,
2727
}))
2828
.filter((attribute) => attribute.value);
2929
});

src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {
3232
SetPageNumber,
3333
} from '@shared/stores';
3434

35-
import { SUBMISSION_REVIEW_OPTIONS } from '../../constants';
35+
import { COLLECTIONS_SUBMISSIONS_REVIEW_OPTIONS } from '../../constants';
3636
import { SubmissionReviewStatus } from '../../enums';
3737
import { CollectionSubmissionsListComponent } from '../collection-submissions-list/collection-submissions-list.component';
3838

@@ -55,7 +55,7 @@ import { CollectionSubmissionsListComponent } from '../collection-submissions-li
5555
export class CollectionModerationSubmissionsComponent {
5656
private router = inject(Router);
5757
private route = inject(ActivatedRoute);
58-
readonly submissionReviewOptions = SUBMISSION_REVIEW_OPTIONS;
58+
readonly submissionReviewOptions = COLLECTIONS_SUBMISSIONS_REVIEW_OPTIONS;
5959

6060
protected collectionProvider = select(CollectionsSelectors.getCollectionProvider);
6161
protected isCollectionProviderLoading = select(CollectionsSelectors.getCollectionProviderLoading);

src/app/features/moderation/components/collection-submission-item/collection-submission-item.component.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { collectionFilterNames } from '@osf/features/collections/constants';
1212
import { SubmissionReviewStatus } from '@osf/features/moderation/enums';
1313
import { IconComponent } from '@osf/shared/components';
1414
import { DateAgoPipe } from '@osf/shared/pipes';
15-
import { CollectionSubmission } from '@shared/models';
15+
import { CollectionSubmissionWithGuid } from '@shared/models';
1616
import { CollectionsSelectors } from '@shared/stores';
1717

1818
import { ReviewStatusIcon } from '../../constants';
@@ -28,7 +28,7 @@ import { ReviewStatusIcon } from '../../constants';
2828
export class CollectionSubmissionItemComponent {
2929
private router = inject(Router);
3030
private activatedRoute = inject(ActivatedRoute);
31-
submission = input.required<CollectionSubmission>();
31+
submission = input.required<CollectionSubmissionWithGuid>();
3232
collectionProvider = select(CollectionsSelectors.getCollectionProvider);
3333

3434
protected readonly reviewStatusIcon = ReviewStatusIcon;
@@ -48,7 +48,7 @@ export class CollectionSubmissionItemComponent {
4848
return collectionFilterNames
4949
.map((attribute) => ({
5050
...attribute,
51-
value: item[attribute.key as keyof CollectionSubmission] as string,
51+
value: item[attribute.key as keyof CollectionSubmissionWithGuid] as string,
5252
}))
5353
.filter((attribute) => attribute.value);
5454
});

src/app/features/moderation/constants/submission.const.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,33 @@ export const SUBMISSION_REVIEW_OPTIONS: SubmissionReviewOption[] = [
2828
},
2929
];
3030

31+
export const COLLECTIONS_SUBMISSIONS_REVIEW_OPTIONS: SubmissionReviewOption[] = [
32+
{
33+
value: SubmissionReviewStatus.Pending,
34+
icon: 'fas fa-hourglass',
35+
label: 'moderation.submissionReviewStatus.pending',
36+
count: 0,
37+
},
38+
{
39+
value: SubmissionReviewStatus.Accepted,
40+
icon: 'fas fa-circle-check',
41+
label: 'moderation.submissionReviewStatus.accepted',
42+
count: 0,
43+
},
44+
{
45+
value: SubmissionReviewStatus.Rejected,
46+
icon: 'fas fa-circle-xmark',
47+
label: 'moderation.submissionReviewStatus.rejected',
48+
count: 0,
49+
},
50+
{
51+
value: SubmissionReviewStatus.Removed,
52+
icon: 'fas fa-circle-minus',
53+
label: 'moderation.submissionReviewStatus.withdrawn',
54+
count: 0,
55+
},
56+
];
57+
3158
export const WITHDRAWAL_SUBMISSION_REVIEW_OPTIONS: SubmissionReviewOption[] = [
3259
{
3360
value: SubmissionReviewStatus.Pending,

src/app/features/moderation/store/collections-moderation/collections-moderation.model.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { CollectionSubmissionReviewAction } from '@osf/features/moderation/models';
2-
import { AsyncStateModel, AsyncStateWithTotalCount, CollectionSubmission } from '@shared/models';
2+
import { AsyncStateModel, AsyncStateWithTotalCount, CollectionSubmissionWithGuid } from '@shared/models';
33

44
export interface CollectionsModerationStateModel {
5-
collectionSubmissions: AsyncStateWithTotalCount<CollectionSubmission[]>;
5+
collectionSubmissions: AsyncStateWithTotalCount<CollectionSubmissionWithGuid[]>;
66
currentReviewAction: AsyncStateModel<CollectionSubmissionReviewAction | null>;
77
}
88

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
@let project = currentProject();
2+
@let submissions = projectSubmissions();
3+
4+
@if (project) {
5+
<h3 class="mb-3">{{ 'project.overview.metadata.collection' | translate }}</h3>
6+
@if (isProjectSubmissionsLoading()) {
7+
<p-skeleton height="3rem" width="100%"></p-skeleton>
8+
} @else {
9+
<div class="flex flex-column gap-3">
10+
@if (submissions.length) {
11+
@for (submission of submissions; track submission.id) {
12+
<div class="metadata-accordion">
13+
<p-accordion>
14+
<p-accordion-panel value="0">
15+
<p-accordion-header class="py-0 gap-2 justify-content-between">
16+
<div class="flex align-items-center gap-3">
17+
<p-button
18+
link
19+
class="link-btn-no-padding"
20+
styleClass="text-left"
21+
(click)="navigateToCollection($event, submission)"
22+
[label]="submission.collectionTitle"
23+
></p-button>
24+
25+
@let status = submission.reviewsState;
26+
@switch (status) {
27+
@case (SubmissionReviewStatus.Accepted) {
28+
<p-tag class="capitalize" [value]="status" severity="success"></p-tag>
29+
}
30+
@case (SubmissionReviewStatus.Rejected) {
31+
<p-tag class="capitalize" [value]="status" severity="danger"></p-tag>
32+
}
33+
@case (SubmissionReviewStatus.Pending) {
34+
<p-tag class="capitalize" [value]="status" severity="warn"></p-tag>
35+
}
36+
@case (SubmissionReviewStatus.Removed) {
37+
<p-tag class="capitalize" [value]="status" severity="secondary"></p-tag>
38+
}
39+
}
40+
</div>
41+
</p-accordion-header>
42+
<p-accordion-content>
43+
@let attributes = submissionAttributes(submission);
44+
@if (attributes.length) {
45+
<div class="flex flex-column gap-2 mt-2">
46+
@for (attribute of attributes; track attribute.key) {
47+
<p class="font-normal">
48+
<span class="font-bold">{{ attribute.label }}:</span> {{ attribute.value }}
49+
</p>
50+
}
51+
</div>
52+
}
53+
</p-accordion-content>
54+
</p-accordion-panel>
55+
</p-accordion>
56+
</div>
57+
}
58+
} @else {
59+
<p>{{ 'project.overview.metadata.noCollections' | translate }}</p>
60+
}
61+
</div>
62+
}
63+
}

src/app/features/project/overview/components/overview-collections/overview-collections.component.scss

Whitespace-only changes.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { OverviewCollectionsComponent } from './overview-collections.component';
4+
5+
describe('OverviewCollectionsComponent', () => {
6+
let component: OverviewCollectionsComponent;
7+
let fixture: ComponentFixture<OverviewCollectionsComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
imports: [OverviewCollectionsComponent],
12+
}).compileComponents();
13+
14+
fixture = TestBed.createComponent(OverviewCollectionsComponent);
15+
component = fixture.componentInstance;
16+
fixture.detectChanges();
17+
});
18+
19+
it('should create', () => {
20+
expect(component).toBeTruthy();
21+
});
22+
});
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { createDispatchMap, select } from '@ngxs/store';
2+
3+
import { TranslatePipe } from '@ngx-translate/core';
4+
5+
import { Accordion, AccordionContent, AccordionHeader, AccordionPanel } from 'primeng/accordion';
6+
import { Button } from 'primeng/button';
7+
import { Skeleton } from 'primeng/skeleton';
8+
import { Tag } from 'primeng/tag';
9+
10+
import { ChangeDetectionStrategy, Component, computed, effect, inject, input } from '@angular/core';
11+
import { Router } from '@angular/router';
12+
13+
import { collectionFilterNames } from '@osf/features/collections/constants';
14+
import { SubmissionReviewStatus } from '@osf/features/moderation/enums';
15+
import { CollectionSubmission, ResourceOverview } from '@shared/models';
16+
import { CollectionsSelectors, GetProjectSubmissions } from '@shared/stores';
17+
18+
@Component({
19+
selector: 'osf-overview-collections',
20+
imports: [Accordion, AccordionPanel, AccordionHeader, AccordionContent, TranslatePipe, Skeleton, Tag, Button],
21+
templateUrl: './overview-collections.component.html',
22+
styleUrl: './overview-collections.component.scss',
23+
changeDetection: ChangeDetectionStrategy.OnPush,
24+
})
25+
export class OverviewCollectionsComponent {
26+
private readonly router = inject(Router);
27+
protected readonly SubmissionReviewStatus = SubmissionReviewStatus;
28+
currentProject = input.required<ResourceOverview | null>();
29+
projectSubmissions = select(CollectionsSelectors.getCurrentProjectSubmissions);
30+
isProjectSubmissionsLoading = select(CollectionsSelectors.getCurrentProjectSubmissionsLoading);
31+
32+
protected projectId = computed(() => {
33+
const resource = this.currentProject();
34+
return resource ? resource.id : null;
35+
});
36+
37+
protected actions = createDispatchMap({
38+
getProjectSubmissions: GetProjectSubmissions,
39+
});
40+
41+
constructor() {
42+
effect(() => {
43+
const projectId = this.projectId();
44+
45+
if (projectId) {
46+
this.actions.getProjectSubmissions(projectId);
47+
}
48+
});
49+
}
50+
51+
protected get submissionAttributes() {
52+
return (submission: CollectionSubmission) => {
53+
if (!submission) return [];
54+
55+
return collectionFilterNames
56+
.map((attribute) => ({
57+
...attribute,
58+
value: submission[attribute.key as keyof CollectionSubmission] as string,
59+
}))
60+
.filter((attribute) => attribute.value);
61+
};
62+
}
63+
64+
navigateToCollection($event: Event, submission: CollectionSubmission) {
65+
$event.stopPropagation();
66+
this.router.navigate([`collections/${submission.collectionId}/`]);
67+
}
68+
}

src/app/shared/components/resource-metadata/resource-metadata.component.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ <h3>{{ 'project.overview.metadata.affiliatedInstitutions' | translate }}</h3>
138138
</div>
139139
</div>
140140

141+
@if (resource.type === resourceTypes.Projects) {
142+
<osf-overview-collections [currentProject]="currentResource()"></osf-overview-collections>
143+
}
144+
141145
<div class="flex flex-column gap-2">
142146
<h3>{{ 'project.overview.metadata.subjects' | translate }}</h3>
143147

0 commit comments

Comments
 (0)