diff --git a/src/app/modules/admin/components/salaries/salaries-admin-paginated-table/salaries-admin-paginated-table.component.ts b/src/app/modules/admin/components/salaries/salaries-admin-paginated-table/salaries-admin-paginated-table.component.ts index a7041ff7..e4335dd5 100644 --- a/src/app/modules/admin/components/salaries/salaries-admin-paginated-table/salaries-admin-paginated-table.component.ts +++ b/src/app/modules/admin/components/salaries/salaries-admin-paginated-table/salaries-admin-paginated-table.component.ts @@ -63,7 +63,6 @@ export class SalariesAdminPaginatedTableComponent { 'Are you sure to delete?', () => { this.deleteRequested.emit(salary); - console.log('deleteRequested'); } ) ); diff --git a/src/app/modules/salaries/components/professions-distribution-chart/people-distribution-chart-object.ts b/src/app/modules/salaries/components/professions-distribution-chart/people-distribution-chart-object.ts new file mode 100644 index 00000000..2f7fa569 --- /dev/null +++ b/src/app/modules/salaries/components/professions-distribution-chart/people-distribution-chart-object.ts @@ -0,0 +1,103 @@ +import { Chart } from 'chart.js/auto'; +import { RandomRgbColor } from '../random-rgb-color'; +import { UserSalary } from '@models/salaries/salary.model'; +import { UserProfession, UserProfessionEnum } from '@models/salaries/user-profession'; + +interface ChartDatasetType { + label: string; + data: Array; + backgroundColor: Array; +} + +export class PeopleDistributionChartObject extends Chart { + + private readonly datasets: Array = []; + + constructor( + canvasId: string, + salaries: Array, + otherLimit: number, + title: string) { + const datasets: Array = []; + + let professions: Array<{profession: UserProfession, label: string, count: number}> = []; + salaries.forEach(x => { + + const existingItem = professions.find(p => p.profession === x.profession); + if (existingItem != null) { + existingItem.count++; + return; + } + + professions.push({ + profession: x.profession, + label: UserProfessionEnum.label(x.profession), + count: 1, + }); + }); + + professions = professions.sort((a, b) => b.count - a.count); + + const professionsToInclude = professions.filter((x) => x.count > otherLimit); + const salariesNotINcluded = professions.filter((x) => x.count <= otherLimit); + + if (salaries.length > 0) { + + const dataForDataset = professionsToInclude.map(x => { + return { + value: (salaries.filter(s => s.profession === x.profession).length / salaries.length) * 100, + color: new RandomRgbColor().toString(0.8), + }; + }); + + if (salariesNotINcluded.length > 0) { + const count = salariesNotINcluded.map(x => x.count).reduce((a, b) => a + b); + dataForDataset.push({ + value: count / salaries.length * 100, + color: new RandomRgbColor().toString(0.8), + }); + } + + datasets.push( + { + label: "Процентное соотношение", + data: dataForDataset.map(x => x.value), + backgroundColor: dataForDataset.map(x => x.color), + } + ); + } + + const labels = professionsToInclude.map(x => x.label); + if (salariesNotINcluded.length > 0) { + labels.push("Другие"); + } + + super( + canvasId, + { + type: 'doughnut', + data: { + labels: labels, + datasets: datasets, + }, + options: { + responsive: true, + plugins: { + legend: { + position: 'left', + title: { + text: title, + font: { + size: 16, + }, + position: 'start', + display: true, + }, + } + } + }, + }); + + this.datasets = datasets; + } +} \ No newline at end of file diff --git a/src/app/modules/salaries/components/professions-distribution-chart/people-distribution-chart.component.html b/src/app/modules/salaries/components/professions-distribution-chart/people-distribution-chart.component.html new file mode 100644 index 00000000..e16b4cbe --- /dev/null +++ b/src/app/modules/salaries/components/professions-distribution-chart/people-distribution-chart.component.html @@ -0,0 +1,20 @@ +
+
Распределение по специальностям
+ +
График отображает распределение респондентов по специальностям в процентном соотношении к общему числу
+
+ +
+
+ {{chartDataLocal}} +
+
+ +
+
+ {{chartDataRemote}} +
+
+ +
+
\ No newline at end of file diff --git a/src/app/modules/salaries/components/professions-distribution-chart/people-distribution-chart.component.scss b/src/app/modules/salaries/components/professions-distribution-chart/people-distribution-chart.component.scss new file mode 100644 index 00000000..b5b84b83 --- /dev/null +++ b/src/app/modules/salaries/components/professions-distribution-chart/people-distribution-chart.component.scss @@ -0,0 +1,15 @@ +canvas { + min-height: 250px; +} + +#canvas-container { + position: relative; + width: 100%; + height: 100%; + min-height: 250px; + max-height: 500px; +} + +.list-group-item { + font-size: smaller; +} \ No newline at end of file diff --git a/src/app/modules/salaries/components/professions-distribution-chart/people-distribution-chart.component.spec.ts b/src/app/modules/salaries/components/professions-distribution-chart/people-distribution-chart.component.spec.ts new file mode 100644 index 00000000..e3d08117 --- /dev/null +++ b/src/app/modules/salaries/components/professions-distribution-chart/people-distribution-chart.component.spec.ts @@ -0,0 +1,28 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { mostUsedImports, testUtilStubs, mostUsedServices } from '@shared/test-utils'; +import { PeopleDistributionChartComponent } from './people-distribution-chart.component'; + +describe('PeopleDistributionChartComponent', () => { + let component: PeopleDistributionChartComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [PeopleDistributionChartComponent], + imports: [...mostUsedImports], + providers: [...testUtilStubs, ...mostUsedServices], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }) + .compileComponents(); + + fixture = TestBed.createComponent(PeopleDistributionChartComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/modules/salaries/components/professions-distribution-chart/people-distribution-chart.component.ts b/src/app/modules/salaries/components/professions-distribution-chart/people-distribution-chart.component.ts new file mode 100644 index 00000000..f56ce699 --- /dev/null +++ b/src/app/modules/salaries/components/professions-distribution-chart/people-distribution-chart.component.ts @@ -0,0 +1,72 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { UserSalary, UserSalaryAdminDto } from '@models/salaries/salary.model'; +import { PeopleDistributionChartObject } from './people-distribution-chart-object'; +import { SalariesChart } from '../salaries-chart/salaries-chart'; +import { CompanyType } from '@models/salaries/company-type'; + +@Component({ + selector: 'app-people-distribution-chart', + templateUrl: './people-distribution-chart.component.html', + styleUrl: './people-distribution-chart.component.scss' +}) +export class PeopleDistributionChartComponent { + + @Input() + chart: SalariesChart | null = null; + + chartDataLocal: PeopleDistributionChartObject | null = null; + chartDataRemote: PeopleDistributionChartObject | null = null; + + readonly canvasIdLocal = 'canvas_' + Math.random().toString(36); + readonly canvasIdRemote = 'canvas_' + Math.random().toString(36); + + constructor() {} + + ngAfterViewInit() { + this.initChart(); + } + + private initChart(): void { + if (this.chart == null || this.chart.salaries.length == 0) { + return; + } + + const localSalaries = this.chart.salaries.filter(x => x.company === CompanyType.Local); + const remoteSalaries = this.chart.salaries.filter(x => x.company === CompanyType.Remote); + + if (localSalaries.length > 0) { + this.chartDataLocal = this.initChartWithParams( + this.canvasIdLocal, + localSalaries, + 10, + "Казахстан"); + } + + if (remoteSalaries.length > 0) { + this.chartDataRemote = this.initChartWithParams( + this.canvasIdRemote, + remoteSalaries, + 3, + "Мир (удаленка)"); + } + } + + private initChartWithParams( + canvasId: string, + salaries: Array, + otherLimit: number, + title: string): PeopleDistributionChartObject { + const chart = new PeopleDistributionChartObject( + canvasId, + salaries, + otherLimit, + title); + + var chartEl = document.getElementById(canvasId); + if (chartEl != null && chartEl.parentElement != null) { + chartEl.style.height = chartEl?.parentElement.style.height ?? '100%'; + } + + return chart; + } +} diff --git a/src/app/modules/salaries/components/salaries-chart/salaries-chart.component.html b/src/app/modules/salaries/components/salaries-chart/salaries-chart.component.html index 9ca80007..b8fa4626 100644 --- a/src/app/modules/salaries/components/salaries-chart/salaries-chart.component.html +++ b/src/app/modules/salaries/components/salaries-chart/salaries-chart.component.html @@ -113,6 +113,12 @@ > +
+ +
+
; } -export interface SalariesByMoneyBarChartItem { - // TODO mgorbatyuk: remove this model - count: number; -} - export interface CreateSalaryRecordResponse { isSuccess: boolean; errorMessage: string | null;