Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ export class SalariesByGradesChartComponent implements OnInit, OnDestroy {
constructor() {}

ngOnInit(): void {
if (this.chart == null) {
if (this.chart == null ||
this.chart.salariesByMoneyBarChart == null) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,41 +1,35 @@
<app-page-header>Зарплаты в Казахстане</app-page-header>
<app-page-header>Зарплаты IT в Казахстане</app-page-header>

<div class="container my-5" *ngIf="salariesChart != null; else dataLoading">
<div class="card">
<div class="card-body">
<div class="mb-3">
На графике можно увидеть зарплаты в Казахстане по разным специальностям IT. Данные заполняются самими пользователями.
</div>
<div *ngIf="showDataStub" class="container mt-5 mb-3">

<div class="mb-3">
Вы сможете посмотреть график только после добавления хотя бы одной зарплаты за последний год.
</div>
<div *ngIf="!isAuthenticated" class="alert alert-primary" role="alert">
<div class="mb-2 h3">Ваша зарплата «в рынке»?</div>
<div class="mb-3">Зарегистрируйтесь и оставьте данные о своей зарплате, чтобы узнать, сколько зарабатывают другие IT-специалисты в Казахстане.</div>
<div>
<button class="btn btn-primary" (click)="openAddSalaryAction()">Зарегистрироваться</button>
</div>
</div>

<div class="mb-3">
<i>TODO: в скором времени будут добавлены график и возможность фльтрации данных по специальностям, грейдам и фреймворкам/языкам программирования</i>
</div>
<div *ngIf="isAuthenticated" class="alert alert-warning" role="alert">
<div class="mb-2 h3">Ваша зарплата «в рынке»?</div>
<div class="mb-3">Оставьте данные о своей зарплате, чтобы узнать, сколько зарабатывают другие IT-специалисты в Казахстане.</div>
<div>
<button class="btn btn-dark" (click)="openAddSalaryAction()">Добавить зарплату</button>
</div>
</div>
</div>

<div class="container mb-5" *ngIf="salariesChart != null; else dataLoading">
<div class="card">
<div class="card-body">

<div *ngIf="salariesChart" class="row">
<div class="col-3">
<div class="mb-3 text-muted">Квалификации:</div>

<ul class="list-group list-group-flush">
<li class="list-group-item">
<div class="small text-muted">Всего зарплат</div>
<div class="h4">{{ salariesChart.countOfRecords }}</div>
</li>

<li class="list-group-item">
<div class="small text-muted">Специальностей</div>
<div class="h4">{{ salariesChart.salariesByProfession?.length ?? "-" }}</div>
</li>

<li class="list-group-item">
<div class="small text-muted">Действия</div>
<div class="">
<button class="btn btn-outline-primary btn-sm" (click)="openAddSalaryAction()">Добавить зарплату</button>
</div>
</li>
</ul>
<div title="Coming soon" class="mb-3" *ngFor="let grade of grades">
<button disabled type="button" class="text-start btn btn-light w-100" >{{ grade }}</button>
</div>
</div>

<div class="col-9 ps-5">
Expand All @@ -47,21 +41,38 @@
</div>
</div>

<div class="mb-5">
<div class="mb-3">
<div class="text-muted">Средняя зарплата:</div>
<div class="">
<span class="display-2 me-3">{{ salariesChart.averageSalary }}</span>
<span class="h4 text-muted">тенге.</span>
</div>
</div>

<div *ngIf="salariesChart.salariesByMoneyBarChart" class="mb-5">
<div class="mb-3">График зарплат по профессиям</div>
<div *ngIf="!showDataStub" class="mb-3 fst-italic">
<div class="text-muted">Рассчитано на основании {{ salariesChart.countOfRecords }} анкет(ы)</div>
</div>

<div *ngIf="showDataStub" class="mb-3">
<div class="text-muted">Данные сгенерированы для демонстрации.
<button class="btn btn-outline-secondary btn-sm" (click)="openAddSalaryAction()">{{ isAuthenticated ? 'Добавить зарплату' : 'Зарегистрироваться' }}</button> для получения актуальных данных.</div>
</div>

<div *ngIf="salariesChart.salariesByMoneyBarChart" class="mt-5 mb-5">
<div class="mb-3 h3">По всем IT-специалистам</div>
<div class="mb-3">
<i>TODO: в скором времени будет добавлена возможность фильтрации данных по специальностям, грейдам и фреймворкам/языкам программирования</i>
</div>

<app-salaries-by-grades-chart [chart]="salariesChart"></app-salaries-by-grades-chart>
</div>

</div>
</div>

<div class="mb-3 text-muted text-end fst-italic">
Идея подсмотрена у сервиса <a class="text-reset" href="https://career.habr.com/salaries" target="_blank">Habr.Карьера</a>, за что им большая благодарность.
</div>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { untilDestroyed } from '@shared/subscriptions/until-destroyed';
import { SalariesChart } from './salaries-chart';
import { AuthService } from '@shared/services/auth/auth.service';
import { CookieService } from 'ngx-cookie-service';
import { StubSalariesChart } from './stub-salaries-chart';

@Component({
templateUrl: './salaries-chart.component.html',
Expand All @@ -15,8 +16,16 @@ import { CookieService } from 'ngx-cookie-service';
export class SalariesChartComponent implements OnInit, OnDestroy {

salariesChart: SalariesChart | null = null;

showDataStub = false;
openAddSalaryModal = false;
isAuthenticated = false;

readonly grades: Array<string> = [
"Junior",
"Middle",
"Senior",
"Lead",
];

constructor(
private readonly service: UserSalariesService,
Expand All @@ -28,29 +37,40 @@ export class SalariesChartComponent implements OnInit, OnDestroy {
}

ngOnInit(): void {
if (this.authService.isAuthenticated()) {
this.isAuthenticated = this.authService.isAuthenticated();
if (this.isAuthenticated) {
this.load();
return;
}

console.log('Saving url to cookie', this.router.url);
this.cookieService.set('url', this.router.url);
this.authService.login();
this.showDataStub = true;
this.salariesChart = new StubSalariesChart();
}

load(): void {
this.service.charts()
.pipe(untilDestroyed(this))
.subscribe((x) => {
this.salariesChart = new SalariesChart(x);
if (x.shouldAddOwnSalary) {
this.openAddSalaryModal = true;
this.showDataStub = true;
this.salariesChart = new StubSalariesChart();
} else {
this.salariesChart = new SalariesChart(x);
this.showDataStub = false;
}
});
}

openAddSalaryAction(): void {
this.openAddSalaryModal = true;
if (this.authService.isAuthenticated()) {
this.openAddSalaryModal = true;
return;
}

console.log('Saving url to cookie', this.router.url);
this.cookieService.set('url', this.router.url);
this.authService.login();
}

closeAddSalaryAction(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export class SalariesChart {
readonly medianSalary: string;
readonly countOfRecords: number;
readonly salariesByProfession: Array<SalariesByProfession>;
readonly salariesByMoneyBarChart: SalariesByMoneyBarChart;
readonly salariesByMoneyBarChart: SalariesByMoneyBarChart | null;

constructor(readonly data: SalariesChartResponse) {
this.averageSalary = formatNumber(data.averageSalary, 'en-US', '1.0-2');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Currency } from "@models/salaries/currency";
import { SalariesChart } from "./salaries-chart";
import { CompanyType } from "@models/salaries/company-type";
import { DeveloperGrade } from "@models/enums";
import { UserProfession } from "@models/salaries/user-profession";

export class StubSalariesChart extends SalariesChart {

private static readonly salaryLabels = [
"250000",
"500000",
"750000",
"1000000",
"1250000",
"1500000",
]

constructor() {
const salariesCount = 10;
const thisYearDate = new Date();
const salaries = [];

for (let index = 0; index < salariesCount; index++) {
salaries.push(
{
value: StubSalariesChart.getRandomNumber(1_500_000, 250_000),
quarter: 1,
year: thisYearDate.getFullYear(),
currency: Currency.KZT,
company: CompanyType.Local,
grade: DeveloperGrade.Middle,
profession: UserProfession.Developer, // TODO mgorbatyuk: randomize
createdAt: thisYearDate,
});
}

super({
averageSalary: 450_000,
medianSalary: 356_000,
shouldAddOwnSalary: true,
salaries: salaries,
salariesByProfession: [],
salariesByMoneyBarChart: {
items: StubSalariesChart.salaryLabels.map((x) => {
const value = parseInt(x);
return {
start: value,
end: value + 250_000,
count: StubSalariesChart.getRandomNumber(100, 25),
};
}),
itemsByProfession: [],
labels: StubSalariesChart.salaryLabels,
},
rangeStart: new Date(),
rangeEnd: new Date(),
});
}

private static getRandomNumber(max: number, min: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
}
2 changes: 1 addition & 1 deletion src/app/services/user-salaries.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export interface SalariesChartResponse {
averageSalary: number;
medianSalary: number;
salariesByProfession: SalariesByProfession[];
salariesByMoneyBarChart: SalariesByMoneyBarChart;
salariesByMoneyBarChart: SalariesByMoneyBarChart | null;
}

export interface SalariesByProfession {
Expand Down