diff --git a/src/app/portal/components/home-page/home-page.component.html b/src/app/portal/components/home-page/home-page.component.html index f81416f65..2cc96672e 100644 --- a/src/app/portal/components/home-page/home-page.component.html +++ b/src/app/portal/components/home-page/home-page.component.html @@ -1,259 +1,263 @@ - - -

- - - - - -
-
- - - - - - - -
-
- - - - - - + + +

+ + + + + + + +
+
+ + + + + + + +
+
+ + + + + + diff --git a/src/app/portal/components/results/publications2/publications2.component.ts b/src/app/portal/components/results/publications2/publications2.component.ts index 8128f936f..b7a75006a 100644 --- a/src/app/portal/components/results/publications2/publications2.component.ts +++ b/src/app/portal/components/results/publications2/publications2.component.ts @@ -154,11 +154,8 @@ export class Publications2Component implements OnDestroy { aggregations$ = this.publications2Service.getAggregations(); yearAdditions$ = this.aggregations$.pipe( - map(aggs => - getYearAdditions(aggs).map( - (bucket: any) => ({ year: bucket.key.toString(), count: bucket.doc_count }) - ) ?? [] - ), + map(aggs => getYearAdditions(aggs) + .map((bucket: any) => ({ year: bucket.key.toString(), count: bucket.doc_count })) ?? []), map(aggs => aggs.sort((a, b) => b.year - a.year)) ); diff --git a/src/app/portal/components/tab-button/tab-button.component.html b/src/app/portal/components/tab-button/tab-button.component.html new file mode 100644 index 000000000..37b21a26b --- /dev/null +++ b/src/app/portal/components/tab-button/tab-button.component.html @@ -0,0 +1,12 @@ + + +
+ + +
{{ label }}
+
+ + +
+
+
diff --git a/src/app/portal/components/tab-button/tab-button.component.scss b/src/app/portal/components/tab-button/tab-button.component.scss new file mode 100644 index 000000000..81bca8d41 --- /dev/null +++ b/src/app/portal/components/tab-button/tab-button.component.scss @@ -0,0 +1,56 @@ +.tab-button-layout2 { + display: flex; + justify-content: space-between; + align-items: center; + + background-color: white; + + width: 280px; + height: 45px; + + padding: 12px 18px 12px 12px; + border: none; + box-shadow: 0 0 4px 0 #00000040; + + font-size: 1rem; + font-weight: bold; + line-height: 1.5; + color: #4546B9; + + font-variant-numeric: tabular-nums; + + outline: none; + user-select: none; + + &:hover { + background: #E8E8F5; + border: none; + } + + /*&:focus { + border: 1px solid red; // #4546B9 + } + + &:focus-visible { + border: 10px solid red; // #4546B9 + }*/ + + &:active { + background: #E8E8F5; + box-shadow: none; + + // border: 1px solid purple; + } +} + +/* TODO: Replace with Design Token */ +.link-button { + font-size: 1rem; + font-weight: bold; + line-height: 1.5; + color: #4546B9; +} + +.animated-number { + font-variant-numeric: tabular-nums; +} diff --git a/src/app/portal/components/tab-button/tab-button.component.ts b/src/app/portal/components/tab-button/tab-button.component.ts new file mode 100644 index 000000000..af4b39a6b --- /dev/null +++ b/src/app/portal/components/tab-button/tab-button.component.ts @@ -0,0 +1,52 @@ +import { Component, Input } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { + faAlignLeft, + faBriefcase, + faBullhorn, + faCalculator, + faFileAlt, + faFileLines, + faUniversity, + faUsers +} from '@fortawesome/free-solid-svg-icons'; +import { CountUpModule } from 'ngx-countup'; +import { RouterLinkWithHref } from '@angular/router'; + +@Component({ + selector: 'app-tab-button', + standalone: true, + imports: [CommonModule, FontAwesomeModule, CountUpModule, RouterLinkWithHref], + templateUrl: './tab-button.component.html', + styleUrls: ['./tab-button.component.scss'] +}) +export class TabButtonComponent { + @Input() label: string; + @Input() count: number; + @Input() route: string; + + countOps = { + duration: 0.5, + separator: ' ', + }; + + protected _icon = faFileLines; + + private iconMap = { + 'faFileLines': faFileLines, + 'faFileAlt': faFileAlt, + 'faUsers': faUsers, + 'faBriefcase': faBriefcase, + 'faAlignLeft': faAlignLeft, + 'faBullhorn': faBullhorn, + 'faCalculator': faCalculator, + 'faUniversity': faUniversity, + }; + + @Input() set icon(iconName: string) { + if (this.iconMap[iconName]) { + this._icon = this.iconMap[iconName]; + } + } +} diff --git a/src/app/portal/components/tab-navigation/tab-navigation.component.html b/src/app/portal/components/tab-navigation/tab-navigation.component.html new file mode 100644 index 000000000..2a3262a0c --- /dev/null +++ b/src/app/portal/components/tab-navigation/tab-navigation.component.html @@ -0,0 +1,34 @@ +
+ +
+ + + +
+
Näytä lisää
+ +
+
+ + +
+
Näytä vähemmän
+ +
+
+
+ + + + + diff --git a/src/app/portal/components/tab-navigation/tab-navigation.component.scss b/src/app/portal/components/tab-navigation/tab-navigation.component.scss new file mode 100644 index 000000000..e036e16d9 --- /dev/null +++ b/src/app/portal/components/tab-navigation/tab-navigation.component.scss @@ -0,0 +1,31 @@ +.navigation-layout { + display: grid; + grid-template-columns: repeat(1, 1fr); + grid-column-gap: 16px; + grid-row-gap: 7px; + + @media (min-width: 768px) { + grid-template-columns: repeat(2, 1fr); + } + + @media (min-width: 990px) { + grid-template-columns: repeat(3, 1fr); + } + + @media (min-width: 1200px) { + grid-template-columns: repeat(4, 1fr); + } +} + +.expand-text { + display: flex; + justify-content: center; + align-items: center; + + font-size: 1rem; + font-weight: 700; + line-height: 1.5; + color: #4546B9; + + margin-top: 1rem; +} diff --git a/src/app/portal/components/tab-navigation/tab-navigation.component.ts b/src/app/portal/components/tab-navigation/tab-navigation.component.ts new file mode 100644 index 000000000..396ebd457 --- /dev/null +++ b/src/app/portal/components/tab-navigation/tab-navigation.component.ts @@ -0,0 +1,154 @@ +import { Component, inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TabButtonComponent } from '@portal/components/tab-button/tab-button.component'; +import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; +import { map, switchMap } from 'rxjs/operators'; +import { HttpClient } from '@angular/common/http'; +import { AppConfigService } from '@shared/services/app-config-service.service'; +import { BreakpointObserver } from '@angular/cdk/layout'; + +type IndexCounts = { [index: string]: number }; +type ButtonData = { label: string, icon: string, route: string, count: number }; + +@Component({ + selector: 'app-tab-navigation', + standalone: true, + imports: [CommonModule, TabButtonComponent], + templateUrl: './tab-navigation.component.html', + styleUrls: ['./tab-navigation.component.scss'] +}) +export class TabNavigationComponent { + http = inject(HttpClient); + breakpointObserver = inject(BreakpointObserver); + appConfigService = inject(AppConfigService); + + url = this.appConfigService.apiUrl + "publication,person,funding,dataset,funding-call,infrastructure,organization/_search?request_cache=true"; + body = { + "size": 0, + "aggs": { + "_index": { + "filters": { + "filters": { + "publications": { + "match": { + "_index": "publication" + } + }, + "persons": { + "match": { + "_index": "person" + } + }, + "fundings": { + "match": { + "_index": "funding" + } + }, + "datasets": { + "bool": { + "must": [ + { + "match": { + "_index": "dataset" + } + }, + { + "term": { + "isLatestVersion": 1 + } + } + ] + } + }, + "infrastructures": { + "match": { + "_index": "infrastructure" + } + }, + "organizations": { + "match": { + "_index": "organization" + } + }, + "fundingCalls": { + "match": { + "_index": "funding-call" + } + } + } + } + } + } + } + + // showAll = false; + showAll$ = new BehaviorSubject(false); + + counts$: Observable = this.http.post(this.url, this.body).pipe( + map((response: any) => response.aggregations), + map(aggregations => Object.entries(aggregations._index.buckets)), + map(buckets => buckets.reduce((acc, [key, value]) => ({ ...acc, [key]: (value as any).doc_count }), {})) + ); + + /*defaultOrderButtons = [ + { label: 'Publications', icon: 'faFileLines', route: "/results/publications", count$: this.counts$.pipe(map(counts => counts.publications)) }, + { label: 'Persons', icon: 'faUsers', route: "/results/persons", count$: this.counts$.pipe(map(counts => counts.persons)) }, + { label: 'Fundings', icon: 'faBriefcase', route: "/results/fundings", count$: this.counts$.pipe(map(counts => counts.fundings)) }, + { label: 'Datasets', icon: 'faFileAlt', route: "/results/datasets", count$: this.counts$.pipe(map(counts => counts.datasets)) }, + { label: 'Funding Calls', icon: 'faBullhorn', route: "/results/funding-calls", count$: this.counts$.pipe(map(counts => counts.fundingCalls)) }, + { label: 'Infrastructures', icon: 'faUniversity', route: "/results/infrastructures", count$: this.counts$.pipe(map(counts => counts.infrastructures)) }, + { label: 'Organizations', icon: 'faCalculator', route: "/results/organizations", count$: this.counts$.pipe(map(counts => counts.organizations)) }, + ];*/ + + defaultOrderButtons$: Observable = this.counts$.pipe( + map(counts => [ + { label: 'Publications', icon: 'faFileLines', route: "/results/publications", count: counts.publications }, + { label: 'Persons', icon: 'faUsers', route: "/results/persons", count: counts.persons }, + { label: 'Fundings', icon: 'faBriefcase', route: "/results/fundings", count: counts.fundings }, + { label: 'Datasets', icon: 'faFileAlt', route: "/results/datasets", count: counts.datasets }, + { label: 'Funding Calls', icon: 'faBullhorn', route: "/results/funding-calls", count: counts.fundingCalls }, + { label: 'Infrastructures', icon: 'faUniversity', route: "/results/infrastructures", count: counts.infrastructures }, + { label: 'Organizations', icon: 'faCalculator', route: "/results/organizations", count: counts.organizations }, + ]) + ); + + // Buttons sorted by the count value + sortedButtons$: Observable = this.defaultOrderButtons$.pipe( + map(buttons => buttons.sort((a, b) => b.count - a.count)) + ); + + // If the narrowest 768px breakpoint is active use the sorted buttons, otherwise use the default order + responsiveOrder$: Observable = this.breakpointObserver.observe(['(max-width: 768px)']).pipe( + switchMap(result => result.matches ? this.sortedButtons$ : this.defaultOrderButtons$) + ); + + responsiveSize$: Observable = this.breakpointObserver.observe(['(min-width: 1200px)', '(min-width: 990px)', '(min-width: 768px)']).pipe( + map(result => { + if (result.breakpoints['(min-width: 1200px)']) { + return 8; + } else if (result.breakpoints['(min-width: 990px)']) { + return 3; + } else if (result.breakpoints['(min-width: 768px)']) { + return 3; + } else { + return 3; + } + }) + ); + + end$ = combineLatest([this.showAll$, this.responsiveSize$, this.defaultOrderButtons$]).pipe( + map<[boolean, number, ButtonData[]], number>(([showAll, end, buttons]) => showAll ? buttons.length : end) + ); + + isDesktop$ = this.breakpointObserver.observe(['(min-width: 1200px)']).pipe( + map(result => result.matches) + ); + + toggleAll() { + this.showAll$.next(!this.showAll$.value); + } + + trackByLabel(index: number, button: ButtonData) { + return button.label; + } +} diff --git a/src/app/portal/portal.module.ts b/src/app/portal/portal.module.ts index 941a00f0c..4f1020714 100644 --- a/src/app/portal/portal.module.ts +++ b/src/app/portal/portal.module.ts @@ -168,6 +168,7 @@ import { PersonGroupAdditionalComponent } from './components/single/single-perso import { FooterComponent } from '../layout/footer/footer.component'; import { SearchBar2Component } from '@portal/search-bar2/search-bar2.component'; import { FixExternalUrlPipe } from '@portal/pipes/fix-external-url.pipe'; +import { TabNavigationComponent } from '@portal/components/tab-navigation/tab-navigation.component'; @NgModule({ declarations: [ @@ -291,7 +292,8 @@ import { FixExternalUrlPipe } from '@portal/pipes/fix-external-url.pipe'; MatSortModule, FooterComponent, SearchBar2Component, - FixExternalUrlPipe + FixExternalUrlPipe, + TabNavigationComponent ], exports: [DatasetAuthorComponent, FiltersComponent], providers: [ diff --git a/src/app/portal/services/publication2.service.ts b/src/app/portal/services/publication2.service.ts index 964d35d03..99e2108ba 100644 --- a/src/app/portal/services/publication2.service.ts +++ b/src/app/portal/services/publication2.service.ts @@ -754,8 +754,6 @@ function generateAggregationStep(name: SearchParamKey, lookup: Record