diff --git a/src/app/features/admin-institutions/pages/institutions-summary/institutions-summary.component.html b/src/app/features/admin-institutions/pages/institutions-summary/institutions-summary.component.html index 0f657dde5..750c68efb 100644 --- a/src/app/features/admin-institutions/pages/institutions-summary/institutions-summary.component.html +++ b/src/app/features/admin-institutions/pages/institutions-summary/institutions-summary.component.html @@ -11,79 +11,93 @@
-
- -
+ @if (departmentLabels().length > 0) { +
+ +
+ } -
- -
+ @if (projectsLabels().length > 0) { +
+ +
+ } -
- -
+ @if (registrationsLabels().length > 0) { +
+ +
+ } -
- -
+ @if (osfProjectsLabels().length > 0) { +
+ +
+ } -
- -
+ @if (licenceLabels().length > 0) { +
+ +
+ } -
- -
+ @if (addonLabels().length > 0) { +
+ +
+ } -
- -
+ @if (storageLabels().length > 0) { +
+ +
+ }
} diff --git a/src/app/features/admin-institutions/pages/institutions-summary/institutions-summary.component.ts b/src/app/features/admin-institutions/pages/institutions-summary/institutions-summary.component.ts index 150fa0883..af580466a 100644 --- a/src/app/features/admin-institutions/pages/institutions-summary/institutions-summary.component.ts +++ b/src/app/features/admin-institutions/pages/institutions-summary/institutions-summary.component.ts @@ -2,7 +2,7 @@ import { createDispatchMap, select } from '@ngxs/store'; import { TranslatePipe, TranslateService } from '@ngx-translate/core'; -import { ChangeDetectionStrategy, Component, effect, inject, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, effect, inject, OnInit, signal } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { BarChartComponent, LoadingSpinnerComponent, StatisticCardComponent } from '@shared/components'; @@ -45,26 +45,26 @@ export class InstitutionsSummaryComponent implements OnInit { rightsSearch = select(InstitutionsAdminSelectors.getSearchResults); rightsLoading = select(InstitutionsAdminSelectors.getSearchResultsLoading); - departmentLabels: string[] = []; - departmentDataset: DatasetInput[] = []; + departmentLabels = signal([]); + departmentDataset = signal([]); - projectsLabels: string[] = []; - projectDataset: DatasetInput[] = []; + projectsLabels = signal([]); + projectDataset = signal([]); - registrationsLabels: string[] = []; - registrationsDataset: DatasetInput[] = []; + registrationsLabels = signal([]); + registrationsDataset = signal([]); - osfProjectsLabels: string[] = []; - osfProjectsDataset: DatasetInput[] = []; + osfProjectsLabels = signal([]); + osfProjectsDataset = signal([]); - storageLabels: string[] = []; - storageDataset: DatasetInput[] = []; + storageLabels = signal([]); + storageDataset = signal([]); - licenceLabels: string[] = []; - licenceDataset: DatasetInput[] = []; + licenceLabels = signal([]); + licenceDataset = signal([]); - addonLabels: string[] = []; - addonDataset: DatasetInput[] = []; + addonLabels = signal([]); + addonDataset = signal([]); private readonly actions = createDispatchMap({ fetchDepartments: FetchInstitutionDepartments, @@ -75,10 +75,12 @@ export class InstitutionsSummaryComponent implements OnInit { }); constructor() { - effect(() => { - this.setStatisticSummaryData(); - this.setChartData(); - }); + this.setStatisticSummaryDataEffect(); + this.setDepartmentsEffect(); + this.setSummaryMetricsEffect(); + this.setStorageEffect(); + this.setLicenseEffect(); + this.setAddonsEffect(); } ngOnInit(): void { @@ -93,102 +95,131 @@ export class InstitutionsSummaryComponent implements OnInit { } } - private setStatisticSummaryData(): void { - const summary = this.summaryMetrics(); - - if (summary) { - this.statisticsData = [ - { - label: 'adminInstitutions.summary.totalUsers', - value: summary.userCount, - }, - { - label: 'adminInstitutions.summary.totalMonthlyLoggedInUsers', - value: summary.monthlyLoggedInUserCount, - }, - { - label: 'adminInstitutions.summary.totalMonthlyActiveUsers', - value: summary.monthlyActiveUserCount, - }, - { - label: 'adminInstitutions.summary.osfPublicAndPrivateProjects', - value: summary.publicProjectCount + summary.privateProjectCount, - }, - { - label: 'adminInstitutions.summary.osfPublicAndEmbargoedRegistrations', - value: summary.publicRegistrationCount + summary.embargoedRegistrationCount, - }, - { - label: 'adminInstitutions.summary.osfPreprints', - value: summary.publishedPreprintCount, - }, - { - label: 'adminInstitutions.summary.totalPublicFileCount', - value: summary.publicFileCount, - }, - { - label: 'adminInstitutions.summary.totalStorageInGb', - value: this.convertBytesToGB(summary.storageByteCount), - }, - ]; - } + private setStatisticSummaryDataEffect(): void { + effect(() => { + const summary = this.summaryMetrics(); + + if (summary) { + this.statisticsData = [ + { + label: 'adminInstitutions.summary.totalUsers', + value: summary.userCount, + }, + { + label: 'adminInstitutions.summary.totalMonthlyLoggedInUsers', + value: summary.monthlyLoggedInUserCount, + }, + { + label: 'adminInstitutions.summary.totalMonthlyActiveUsers', + value: summary.monthlyActiveUserCount, + }, + { + label: 'adminInstitutions.summary.osfPublicAndPrivateProjects', + value: summary.publicProjectCount + summary.privateProjectCount, + }, + { + label: 'adminInstitutions.summary.osfPublicAndEmbargoedRegistrations', + value: summary.publicRegistrationCount + summary.embargoedRegistrationCount, + }, + { + label: 'adminInstitutions.summary.osfPreprints', + value: summary.publishedPreprintCount, + }, + { + label: 'adminInstitutions.summary.totalPublicFileCount', + value: summary.publicFileCount, + }, + { + label: 'adminInstitutions.summary.totalStorageInGb', + value: this.convertBytesToGB(summary.storageByteCount), + }, + ]; + } + }); } - private setChartData(): void { - const departments = this.departments(); - const summary = this.summaryMetrics(); - const storage = this.storageRegionSearch(); - const licenses = this.rightsSearch(); - const addons = this.hasOsfAddonSearch(); - - this.departmentLabels = departments.map((item) => item.name || ''); - this.departmentDataset = [{ label: '', data: departments.map((item) => item.numberOfUsers) }]; - - this.projectsLabels = ['resourceCard.labels.publicProjects', 'adminInstitutions.summary.privateProjects'].map( - (el) => this.translateService.instant(el) - ); - this.projectDataset = [{ label: '', data: [summary.publicProjectCount, summary.privateProjectCount] }]; - - this.registrationsLabels = [ - 'resourceCard.labels.publicRegistrations', - 'adminInstitutions.summary.embargoedRegistrations', - ].map((el) => this.translateService.instant(el)); - this.registrationsDataset = [ - { label: '', data: [summary.publicRegistrationCount, summary.embargoedRegistrationCount] }, - ]; - - this.osfProjectsLabels = [ - 'adminInstitutions.summary.publicRegistrations', - 'adminInstitutions.summary.embargoedRegistrations', - 'adminInstitutions.summary.publicProjects', - 'adminInstitutions.summary.privateProjects', - 'common.search.tabs.preprints', - ].map((el) => this.translateService.instant(el)); - this.osfProjectsDataset = [ - { - label: '', - data: [ - summary.publicRegistrationCount, - summary.embargoedRegistrationCount, - summary.publicProjectCount, - summary.privateProjectCount, - summary.publishedPreprintCount, - ], - }, - ]; - - this.storageLabels = storage.map((result) => result.label); - this.storageDataset = [{ label: '', data: storage.map((result) => +result.value) }]; - - this.licenceLabels = licenses.map((result) => result.label); - this.licenceDataset = [{ label: '', data: licenses.map((result) => +result.value) }]; - - this.addonLabels = addons.map((result) => result.label); - this.addonDataset = [{ label: '', data: addons.map((result) => +result.value) }]; + private setAddonsEffect(): void { + effect(() => { + const addons = this.hasOsfAddonSearch(); + + this.addonLabels.set(addons.map((result) => result.label)); + this.addonDataset.set([{ label: '', data: addons.map((result) => +result.value) }]); + }); } private convertBytesToGB(bytes: number): string { const gb = bytes / (1024 * 1024 * 1024); return gb.toFixed(1); } + + private setDepartmentsEffect() { + effect(() => { + const departments = this.departments(); + + this.departmentLabels.set(departments.map((item) => item.name || '')); + this.departmentDataset.set([{ label: '', data: departments.map((item) => item.numberOfUsers) }]); + }); + } + + private setSummaryMetricsEffect() { + effect(() => { + const summary = this.summaryMetrics(); + + this.projectsLabels.set( + ['resourceCard.labels.publicProjects', 'adminInstitutions.summary.privateProjects'].map((el) => + this.translateService.instant(el) + ) + ); + this.projectDataset.set([{ label: '', data: [summary.publicProjectCount, summary.privateProjectCount] }]); + + this.registrationsLabels.set( + ['resourceCard.labels.publicRegistrations', 'adminInstitutions.summary.embargoedRegistrations'].map((el) => + this.translateService.instant(el) + ) + ); + this.registrationsDataset.set([ + { label: '', data: [summary.publicRegistrationCount, summary.embargoedRegistrationCount] }, + ]); + + this.osfProjectsLabels.set( + [ + 'adminInstitutions.summary.publicRegistrations', + 'adminInstitutions.summary.embargoedRegistrations', + 'adminInstitutions.summary.publicProjects', + 'adminInstitutions.summary.privateProjects', + 'common.search.tabs.preprints', + ].map((el) => this.translateService.instant(el)) + ); + this.osfProjectsDataset.set([ + { + label: '', + data: [ + summary.publicRegistrationCount, + summary.embargoedRegistrationCount, + summary.publicProjectCount, + summary.privateProjectCount, + summary.publishedPreprintCount, + ], + }, + ]); + }); + } + + private setStorageEffect() { + effect(() => { + const storage = this.storageRegionSearch(); + + this.storageLabels.set(storage.map((result) => result.label)); + this.storageDataset.set([{ label: '', data: storage.map((result) => +result.value) }]); + }); + } + + private setLicenseEffect() { + effect(() => { + const licenses = this.rightsSearch(); + + this.licenceLabels.set(licenses.map((result) => result.label)); + this.licenceDataset.set([{ label: '', data: licenses.map((result) => +result.value) }]); + }); + } } diff --git a/src/app/features/admin-institutions/routes.ts b/src/app/features/admin-institutions/routes.ts index 9d697be2e..d64d488c0 100644 --- a/src/app/features/admin-institutions/routes.ts +++ b/src/app/features/admin-institutions/routes.ts @@ -32,18 +32,22 @@ export const routes: Routes = [ { path: 'users', component: InstitutionsUsersComponent, + data: { scrollToTop: false }, }, { path: 'projects', component: InstitutionsProjectsComponent, + data: { scrollToTop: false }, }, { path: 'registrations', component: InstitutionsRegistrationsComponent, + data: { scrollToTop: false }, }, { path: 'preprints', component: InstitutionsPreprintsComponent, + data: { scrollToTop: false }, }, ], }, diff --git a/src/app/shared/components/bar-chart/bar-chart.component.html b/src/app/shared/components/bar-chart/bar-chart.component.html index 66f2adbd6..99a45b351 100644 --- a/src/app/shared/components/bar-chart/bar-chart.component.html +++ b/src/app/shared/components/bar-chart/bar-chart.component.html @@ -5,24 +5,29 @@

{{ title() | translate }}

@if (isLoading()) { } @else { -
-
- +
+
+ @if (data() && options() && labels().length) { + + }
@if (showExpandedSection()) { -
+
- +

{{ title() | translate }}

-
+
@for (label of labels(); let i = $index; track i) {
  • -
    +
    {{ label }}
    @if (datasets().length) { diff --git a/src/app/shared/components/bar-chart/bar-chart.component.ts b/src/app/shared/components/bar-chart/bar-chart.component.ts index e47946b32..297f98779 100644 --- a/src/app/shared/components/bar-chart/bar-chart.component.ts +++ b/src/app/shared/components/bar-chart/bar-chart.component.ts @@ -4,16 +4,7 @@ import { Accordion, AccordionContent, AccordionHeader, AccordionPanel } from 'pr import { ChartModule } from 'primeng/chart'; import { isPlatformBrowser } from '@angular/common'; -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - inject, - input, - OnInit, - PLATFORM_ID, - signal, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject, input, OnInit, PLATFORM_ID, signal } from '@angular/core'; import { PIE_CHART_PALETTE } from '@osf/shared/constants'; import { DatasetInput } from '@osf/shared/models'; @@ -49,17 +40,13 @@ export class BarChartComponent implements OnInit { orientation = input<'horizontal' | 'vertical'>('horizontal'); showExpandedSection = input(false); - options = signal({}); - data = signal({} as ChartData); + options = signal(null); + data = signal(null); platformId = inject(PLATFORM_ID); - cd = inject(ChangeDetectorRef); + readonly PIE_CHART_PALETTE = PIE_CHART_PALETTE; ngOnInit() { - this.initChart(); - } - - initChart() { if (isPlatformBrowser(this.platformId)) { const documentStyle = getComputedStyle(document.documentElement); const textColorSecondary = documentStyle.getPropertyValue('--dark-blue-1'); @@ -69,8 +56,6 @@ export class BarChartComponent implements OnInit { this.setChartData(defaultBackgroundColor, defaultBorderColor); this.setChartOptions(textColorSecondary, surfaceBorder); - - this.cd.markForCheck(); } } @@ -88,15 +73,12 @@ export class BarChartComponent implements OnInit { }); } - getColor(index: number): string { - return PIE_CHART_PALETTE[index % PIE_CHART_PALETTE.length]; - } - private setChartOptions(textColorSecondary: string, surfaceBorder: string) { this.options.set({ indexAxis: this.orientation() === 'horizontal' ? 'y' : 'x', maintainAspectRatio: false, - aspectRatio: 0.8, + aspectRatio: 0.9, + responsive: true, plugins: { legend: { display: this.showLegend(), diff --git a/src/app/shared/components/doughnut-chart/doughnut-chart.component.html b/src/app/shared/components/doughnut-chart/doughnut-chart.component.html index eefcdeccb..e8c487bfc 100644 --- a/src/app/shared/components/doughnut-chart/doughnut-chart.component.html +++ b/src/app/shared/components/doughnut-chart/doughnut-chart.component.html @@ -5,24 +5,29 @@

    {{ title() | translate }}

    @if (isLoading()) { } @else { -
    -
    - +
    +
    + @if (data() && options() && labels().length) { + + }
    @if (showExpandedSection()) { -
    +
    - +

    {{ title() | translate }}

    -
    +
    @for (label of labels(); let i = $index; track i) {
  • -
    +
    {{ label }}
    @if (datasets().length) { diff --git a/src/app/shared/components/doughnut-chart/doughnut-chart.component.ts b/src/app/shared/components/doughnut-chart/doughnut-chart.component.ts index 718d71c16..88b4f6755 100644 --- a/src/app/shared/components/doughnut-chart/doughnut-chart.component.ts +++ b/src/app/shared/components/doughnut-chart/doughnut-chart.component.ts @@ -4,16 +4,7 @@ import { Accordion, AccordionContent, AccordionHeader, AccordionPanel } from 'pr import { ChartModule } from 'primeng/chart'; import { isPlatformBrowser } from '@angular/common'; -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - inject, - input, - OnInit, - PLATFORM_ID, - signal, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject, input, OnInit, PLATFORM_ID, signal } from '@angular/core'; import { PIE_CHART_PALETTE } from '@osf/shared/constants'; import { DatasetInput } from '@osf/shared/models'; @@ -39,7 +30,6 @@ import { ChartData, ChartOptions } from 'chart.js'; }) export class DoughnutChartComponent implements OnInit { private readonly platformId = inject(PLATFORM_ID); - private readonly cd = inject(ChangeDetectorRef); isLoading = input(false); title = input(''); @@ -48,26 +38,18 @@ export class DoughnutChartComponent implements OnInit { showLegend = input(false); showExpandedSection = input(false); - options = signal({}); - data = signal({} as ChartData); + options = signal | null>(null); + data = signal(null); - ngOnInit() { - this.initChart(); - } + readonly PIE_CHART_PALETTE = PIE_CHART_PALETTE; - initChart() { + ngOnInit() { if (isPlatformBrowser(this.platformId)) { this.setChartData(); this.setChartOptions(); - - this.cd.markForCheck(); } } - getColor(index: number): string { - return PIE_CHART_PALETTE[index % PIE_CHART_PALETTE.length]; - } - private setChartData() { const chartDatasets = this.datasets().map((dataset) => ({ label: dataset.label, @@ -84,6 +66,7 @@ export class DoughnutChartComponent implements OnInit { private setChartOptions() { this.options.set({ + cutout: '60%', maintainAspectRatio: true, responsive: true, plugins: {