From f8ffc5e062dd5c4de1e572c678dde98c545c40c5 Mon Sep 17 00:00:00 2001 From: "maxim.gorbatyuk" Date: Tue, 30 Jan 2024 22:59:18 +0600 Subject: [PATCH 1/5] Added city enum --- src/app/models/salaries/kazakhstan-city.ts | 95 ++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 src/app/models/salaries/kazakhstan-city.ts diff --git a/src/app/models/salaries/kazakhstan-city.ts b/src/app/models/salaries/kazakhstan-city.ts new file mode 100644 index 00000000..f00b10d6 --- /dev/null +++ b/src/app/models/salaries/kazakhstan-city.ts @@ -0,0 +1,95 @@ +import { SelectItem } from "@shared/select-boxes/select-item"; +import { EnumHelper } from "@shared/value-objects/enum-helper"; +import { SplittedByWhitespacesString } from "@shared/value-objects/splitted-by-whitespaces-string"; + +export enum KazakhstanCity { + Undefined = 0, + + Aktau = 1, + + Aktobe = 2, + + Almaty = 3, + + Atyrau = 4, + + Astana = 5, + + Arkalyk = 6, + + Baikonur = 7, + + Balqash = 8, + + Jezkazgan = 9, + + Karaganda = 10, + + Kentau = 11, + + Kyzylorda = 12, + + Kokshetau = 13, + + Kostanay = 14, + + Janaozen = 15, + + Pavlodar = 16, + + Petropavl = 17, + + Ridder = 18, + + Saran = 19, + + Satpaev = 20, + + Semey = 21, + + Stepnogorsk = 22, + + Taldykorgan = 23, + + Taraz = 24, + + Temirtau = 25, + + Turkestan = 26, + + Oral = 27, + + Oskemen = 28, + + Shymkent = 29, + + Shakhtinsk = 30, + + Schuschinsk = 31, + + Ekibastuz = 32, +} + +export class KazakhstanCityEnum { + static options(): Array> { + return KazakhstanCityEnum.allItems().map((item) => { + return { + value: item.toString(), + item: item, + label: KazakhstanCityEnum.label(item) + }; + }); + } + + static allItems(): KazakhstanCity[] { + return EnumHelper.getValues(KazakhstanCity) + .filter((x) => x !== KazakhstanCity.Undefined); + } + + static label(item: KazakhstanCity): string { + switch (item) { + default: + return new SplittedByWhitespacesString(KazakhstanCity[item]).value; + } + } +} \ No newline at end of file From f8d151527d15bf5bdbb9c828a8fb2fbe2db6f023 Mon Sep 17 00:00:00 2001 From: "maxim.gorbatyuk" Date: Wed, 31 Jan 2024 18:27:22 +0600 Subject: [PATCH 2/5] Added city to form --- src/app/models/salaries/kazakhstan-city.ts | 157 +++++++++++++++--- src/app/models/salaries/salary.model.ts | 3 + .../add-salary/add-salary.component.html | 20 ++- .../add-salary/add-salary.component.ts | 2 + .../components/add-salary/edit-salary-form.ts | 18 +- .../edit-salary/edit-salary-form.ts | 12 ++ .../edit-salary/edit-salary.component.html | 17 +- .../edit-salary/edit-salary.component.ts | 2 + src/app/services/user-salaries.service.ts | 3 + .../directives/numbers-only.directive.ts | 15 +- 10 files changed, 216 insertions(+), 33 deletions(-) diff --git a/src/app/models/salaries/kazakhstan-city.ts b/src/app/models/salaries/kazakhstan-city.ts index f00b10d6..59a29c1b 100644 --- a/src/app/models/salaries/kazakhstan-city.ts +++ b/src/app/models/salaries/kazakhstan-city.ts @@ -33,52 +33,62 @@ export enum KazakhstanCity { Kostanay = 14, - Janaozen = 15, + Konaev = 15, - Pavlodar = 16, + Janaozen = 16, - Petropavl = 17, + Pavlodar = 17, - Ridder = 18, + Petropavl = 18, - Saran = 19, + Ridder = 19, - Satpaev = 20, + Saran = 20, - Semey = 21, + Satpaev = 21, - Stepnogorsk = 22, + Semey = 22, - Taldykorgan = 23, + Stepnogorsk = 23, - Taraz = 24, + Taldykorgan = 24, - Temirtau = 25, + Taraz = 25, - Turkestan = 26, + Temirtau = 26, - Oral = 27, + Turkestan = 27, - Oskemen = 28, + Oral = 28, - Shymkent = 29, + Oskemen = 29, - Shakhtinsk = 30, + Shymkent = 30, - Schuschinsk = 31, + Shakhtinsk = 31, - Ekibastuz = 32, + Schuschinsk = 32, + + Ekibastuz = 33, } export class KazakhstanCityEnum { static options(): Array> { - return KazakhstanCityEnum.allItems().map((item) => { + const array = [{ + value: KazakhstanCity.Undefined.toString(), + item: KazakhstanCity.Undefined, + label: 'Живу в другой стране' + }, + ...KazakhstanCityEnum.allItems().map((item) => { return { - value: item.toString(), - item: item, - label: KazakhstanCityEnum.label(item) + value: item.toString(), + item: item, + label: KazakhstanCityEnum.label(item) }; - }); + }), + ]; + + return array; } static allItems(): KazakhstanCity[] { @@ -87,7 +97,106 @@ export class KazakhstanCityEnum { } static label(item: KazakhstanCity): string { - switch (item) { + switch (item) { + case KazakhstanCity.Aktau: + return 'Актау'; + + case KazakhstanCity.Aktobe: + return 'Актобе'; + + case KazakhstanCity.Almaty: + return 'Алматы'; + + case KazakhstanCity.Atyrau: + return 'Атырау'; + + case KazakhstanCity.Astana: + return 'Астана'; + + case KazakhstanCity.Arkalyk: + return 'Аркалык'; + + case KazakhstanCity.Baikonur: + return 'Байконур'; + + case KazakhstanCity.Balqash: + return 'Балхаш'; + + case KazakhstanCity.Jezkazgan: + return 'Жезказган'; + + case KazakhstanCity.Karaganda: + return 'Караганда'; + + case KazakhstanCity.Kentau: + return 'Кентау'; + + case KazakhstanCity.Kyzylorda: + return 'Кызылорда'; + + case KazakhstanCity.Kokshetau: + return 'Кокшетау'; + + case KazakhstanCity.Kostanay: + return 'Костанай'; + + case KazakhstanCity.Konaev: + return 'Кунаев'; + + case KazakhstanCity.Janaozen: + return 'Жанаозен'; + + case KazakhstanCity.Pavlodar: + return 'Павлодар'; + + case KazakhstanCity.Petropavl: + return 'Петропавловск'; + + case KazakhstanCity.Ridder: + return 'Риддер'; + + case KazakhstanCity.Saran: + return 'Саран'; + + case KazakhstanCity.Satpaev: + return 'Сатпаев'; + + case KazakhstanCity.Semey: + return 'Семей'; + + case KazakhstanCity.Stepnogorsk: + return 'Степногорск'; + + case KazakhstanCity.Taldykorgan: + return 'Талдыкорган'; + + case KazakhstanCity.Taraz: + return 'Тараз'; + + case KazakhstanCity.Temirtau: + return 'Темиртау'; + + case KazakhstanCity.Turkestan: + return 'Туркестан'; + + case KazakhstanCity.Oral: + return 'Уральск'; + + case KazakhstanCity.Oskemen: + return 'Усть-Каменогорск'; + + case KazakhstanCity.Shymkent: + return 'Шымкент'; + + case KazakhstanCity.Shakhtinsk: + return 'Шахтинск'; + + case KazakhstanCity.Schuschinsk: + return 'Щучинск'; + + case KazakhstanCity.Ekibastuz: + return 'Экибастуз'; + default: return new SplittedByWhitespacesString(KazakhstanCity[item]).value; } diff --git a/src/app/models/salaries/salary.model.ts b/src/app/models/salaries/salary.model.ts index 7fd6bed3..89255028 100644 --- a/src/app/models/salaries/salary.model.ts +++ b/src/app/models/salaries/salary.model.ts @@ -2,6 +2,7 @@ import { DeveloperGrade } from "@models/enums"; import { CompanyType } from "./company-type"; import { Currency } from "./currency"; import { UserProfession } from "./user-profession"; +import { KazakhstanCity } from "./kazakhstan-city"; export interface UserSalaryAdminDto extends UserSalary{ id: string; @@ -15,5 +16,7 @@ export interface UserSalary { company: CompanyType; grade: DeveloperGrade | null; profession: UserProfession; + city: KazakhstanCity | null; + skillId: number | null; createdAt: Date; } diff --git a/src/app/modules/salaries/components/add-salary/add-salary.component.html b/src/app/modules/salaries/components/add-salary/add-salary.component.html index 644fcbd7..383f69c6 100644 --- a/src/app/modules/salaries/components/add-salary/add-salary.component.html +++ b/src/app/modules/salaries/components/add-salary/add-salary.component.html @@ -21,7 +21,7 @@ (ngSubmit)="addSalarySubmitAction()">
- +
@@ -71,13 +71,29 @@
+
+
+
+ + + +
+
{{ errorMessage }}
diff --git a/src/app/modules/salaries/components/add-salary/add-salary.component.ts b/src/app/modules/salaries/components/add-salary/add-salary.component.ts index 395357de..8f0938f2 100644 --- a/src/app/modules/salaries/components/add-salary/add-salary.component.ts +++ b/src/app/modules/salaries/components/add-salary/add-salary.component.ts @@ -8,6 +8,7 @@ import { UserSalary } from '@models/salaries/salary.model'; import { AlertService } from '@shared/components/alert/services/alert.service'; import { UserProfession, UserProfessionEnum } from '@models/salaries/user-profession'; import { SelectItem } from '@shared/select-boxes/select-item'; +import { KazakhstanCity, KazakhstanCityEnum } from '@models/salaries/kazakhstan-city'; @Component({ selector: 'app-add-salary-modal', @@ -28,6 +29,7 @@ export class AddSalaryComponent implements OnInit, OnDestroy { readonly companyTypes: Array = CompanyTypeSelectItem.allItems(); readonly grades: Array = DeveloperGradeSelectItem.gradesSimpleOnly(); readonly professions: Array> = UserProfessionEnum.options(true); + readonly cities: Array> = KazakhstanCityEnum.options(); constructor( private readonly service: UserSalariesService, diff --git a/src/app/modules/salaries/components/add-salary/edit-salary-form.ts b/src/app/modules/salaries/components/add-salary/edit-salary-form.ts index 008f6792..ca95fe3e 100644 --- a/src/app/modules/salaries/components/add-salary/edit-salary-form.ts +++ b/src/app/modules/salaries/components/add-salary/edit-salary-form.ts @@ -1,7 +1,9 @@ +import { formatNumber } from "@angular/common"; import { FormControl, FormGroup, Validators } from "@angular/forms"; import { DeveloperGrade } from "@models/enums"; import { CompanyType } from "@models/salaries/company-type"; import { Currency } from "@models/salaries/currency"; +import { KazakhstanCity } from "@models/salaries/kazakhstan-city"; import { UserSalary } from "@models/salaries/salary.model"; import { UserProfession } from "@models/salaries/user-profession"; import { CreateUserSalaryRequest } from "@services/user-salaries.service"; @@ -13,10 +15,13 @@ static readonly digitsPattern = '^[0-9]*$'; constructor(private readonly salarytoBeEdited: UserSalary | null = null) { const now = new Date(Date.now()); const currentQuarter = Math.floor((now.getMonth() + 3) / 3); + const salaryValue = salarytoBeEdited?.value != null + ? formatNumber(salarytoBeEdited.value, 'en-US', '1.0-2') + : null; super({ value: new FormControl( - salarytoBeEdited?.value ?? null, + salaryValue, [ Validators.pattern(EditSalaryForm.digitsPattern), Validators.required, @@ -43,6 +48,8 @@ static readonly digitsPattern = '^[0-9]*$'; company: new FormControl(salarytoBeEdited?.company ?? null, [Validators.required]), grade: new FormControl(salarytoBeEdited?.grade ?? null, [Validators.required]), profession: new FormControl(salarytoBeEdited?.profession ?? null, [Validators.required]), + city: new FormControl(salarytoBeEdited?.city ?? null, []), + skillId: new FormControl(salarytoBeEdited?.skillId ?? null, []), }); } @@ -50,6 +57,13 @@ static readonly digitsPattern = '^[0-9]*$'; if (this.valid) { const profession = Number(this.value.profession) as UserProfession; const grade = Number(this.value.grade) as DeveloperGrade; + const city = this.value.city != null + ? Number(this.value.city) as KazakhstanCity + : null; + + const skillId = this.value.skillId != null + ? Number(this.value.skillId) + : null; return { value: Number(this.value.value), @@ -59,6 +73,8 @@ static readonly digitsPattern = '^[0-9]*$'; company: Number(this.value.company) as CompanyType, grade: grade, profession: profession, + city: city != KazakhstanCity.Undefined ? city : null, + skillId: skillId, }; } diff --git a/src/app/modules/salaries/components/edit-salary/edit-salary-form.ts b/src/app/modules/salaries/components/edit-salary/edit-salary-form.ts index 4da0b52e..76d7b79b 100644 --- a/src/app/modules/salaries/components/edit-salary/edit-salary-form.ts +++ b/src/app/modules/salaries/components/edit-salary/edit-salary-form.ts @@ -1,5 +1,6 @@ import { FormControl, FormGroup, Validators } from "@angular/forms"; import { DeveloperGrade } from "@models/enums"; +import { KazakhstanCity } from "@models/salaries/kazakhstan-city"; import { UserSalary } from "@models/salaries/salary.model"; import { UserProfession } from "@models/salaries/user-profession"; import { EditUserSalaryRequest } from "@services/user-salaries.service"; @@ -12,6 +13,8 @@ static readonly digitsPattern = '^[0-9]*$'; super({ grade: new FormControl(salarytoBeEdited?.grade ?? null, [Validators.required]), profession: new FormControl(salarytoBeEdited?.profession ?? null, [Validators.required]), + city: new FormControl(salarytoBeEdited?.city ?? null, []), + skillId: new FormControl(salarytoBeEdited?.skillId ?? null, []), }); } @@ -19,10 +22,19 @@ static readonly digitsPattern = '^[0-9]*$'; if (this.valid) { const grade = Number(this.value.grade) as DeveloperGrade; const profession = Number(this.value.profession) as UserProfession; + const city = this.value.city != null + ? Number(this.value.city) as KazakhstanCity + : null; + + const skillId = this.value.skillId != null + ? Number(this.value.skillId) + : null; return { grade: grade, profession: profession, + city: city != KazakhstanCity.Undefined ? city : null, + skillId: skillId, }; } diff --git a/src/app/modules/salaries/components/edit-salary/edit-salary.component.html b/src/app/modules/salaries/components/edit-salary/edit-salary.component.html index 2cd1864b..76148a29 100644 --- a/src/app/modules/salaries/components/edit-salary/edit-salary.component.html +++ b/src/app/modules/salaries/components/edit-salary/edit-salary.component.html @@ -33,7 +33,7 @@
- +
+
+
+ + + +
+
{{ errorMessage }}
diff --git a/src/app/modules/salaries/components/edit-salary/edit-salary.component.ts b/src/app/modules/salaries/components/edit-salary/edit-salary.component.ts index 09e8cae2..b18a22cd 100644 --- a/src/app/modules/salaries/components/edit-salary/edit-salary.component.ts +++ b/src/app/modules/salaries/components/edit-salary/edit-salary.component.ts @@ -8,6 +8,7 @@ import { UserSalary, UserSalaryAdminDto } from '@models/salaries/salary.model'; import { AlertService } from '@shared/components/alert/services/alert.service'; import { UserProfession, UserProfessionEnum } from '@models/salaries/user-profession'; import { SelectItem } from '@shared/select-boxes/select-item'; +import { KazakhstanCity, KazakhstanCityEnum } from '@models/salaries/kazakhstan-city'; @Component({ selector: 'app-edit-salary-modal', @@ -31,6 +32,7 @@ export class EditSalaryComponent implements OnInit, OnDestroy { readonly companyTypes: Array = CompanyTypeSelectItem.allItems(); readonly grades: Array = DeveloperGradeSelectItem.gradesSimpleOnly(); readonly professions: Array> = UserProfessionEnum.options(); + readonly cities: Array> = KazakhstanCityEnum.options(); constructor( private readonly service: UserSalariesService, diff --git a/src/app/services/user-salaries.service.ts b/src/app/services/user-salaries.service.ts index d4a8d0b2..4952f66c 100644 --- a/src/app/services/user-salaries.service.ts +++ b/src/app/services/user-salaries.service.ts @@ -9,6 +9,7 @@ import { UserProfession } from '@models/salaries/user-profession'; import { PageParams, defaultPageParams } from '@models/page-params'; import { PaginatedList } from '@models/paginated-list'; import { ConvertObjectToHttpParams } from '@shared/value-objects/convert-object-to-http'; +import { KazakhstanCity } from '@models/salaries/kazakhstan-city'; export interface CreateUserSalaryRequest extends EditUserSalaryRequest { value: number; @@ -21,6 +22,8 @@ export interface CreateUserSalaryRequest extends EditUserSalaryRequest { export interface EditUserSalaryRequest { grade: DeveloperGrade; profession: UserProfession; + city: KazakhstanCity | null; + skillId: number | null; } export interface SalariesChartResponse { diff --git a/src/app/shared/directives/numbers-only.directive.ts b/src/app/shared/directives/numbers-only.directive.ts index 010cd0d4..c35bb89a 100644 --- a/src/app/shared/directives/numbers-only.directive.ts +++ b/src/app/shared/directives/numbers-only.directive.ts @@ -1,12 +1,13 @@ -import { ElementRef, HostListener, Directive } from '@angular/core'; +import { ElementRef, HostListener, Directive, Input } from '@angular/core'; @Directive({ selector: '[appOnlyNumber]' }) // soure: https://www.bennettnotes.com/angular-4-input-numbers-directive/ export class OnlyNumberDirective { + // Allow decimal numbers and negative values - private readonly regex = new RegExp(/^-?[0-9]+(\.[0-9]*){0,1}$/g); + private readonly onlyDigitsRegex = new RegExp(/^-?[0-9]+(\.[0-9]*){0,1}$/g); // Allow key codes for special events. Reflect : // Backspace, tab, end, home @@ -37,7 +38,7 @@ export class OnlyNumberDirective { const current = this.el!.nativeElement.value as string; const next = current.concat(event.key); - if (this.isNotDigit(next)) { + if (!this.isDigit(next)) { event.preventDefault(); } } @@ -48,7 +49,11 @@ export class OnlyNumberDirective { } // public is for test purposes - isNotDigit(value: string): boolean { - return value != null && value !== '' && !String(value).match(this.regex); + isDigit(value: string): boolean { + if (value == null || value === '') { + return false; + } + + return value.match(this.onlyDigitsRegex) != null; } } From 54049d383d4a2312ddefb41ce886972c1b5b6bf4 Mon Sep 17 00:00:00 2001 From: "maxim.gorbatyuk" Date: Wed, 31 Jan 2024 18:32:19 +0600 Subject: [PATCH 3/5] Adjusted admin --- .../salaries-admin-paginated-table.component.html | 2 ++ src/app/modules/admin/components/salaries/salary-admin-item.ts | 3 +++ 2 files changed, 5 insertions(+) diff --git a/src/app/modules/admin/components/salaries/salaries-admin-paginated-table/salaries-admin-paginated-table.component.html b/src/app/modules/admin/components/salaries/salaries-admin-paginated-table/salaries-admin-paginated-table.component.html index e0cc82d4..2988d007 100644 --- a/src/app/modules/admin/components/salaries/salaries-admin-paginated-table/salaries-admin-paginated-table.component.html +++ b/src/app/modules/admin/components/salaries/salaries-admin-paginated-table/salaries-admin-paginated-table.component.html @@ -54,6 +54,7 @@ Profession Grade Company + City Created at @@ -64,6 +65,7 @@ {{ item.profession }} {{ item.grade }} {{ item.company }} + {{ item.city }} {{ item.createdAt | date: 'yyyy-MM-dd HH:mm' }} или к статистике. diff --git a/src/app/modules/salaries/components/salaries-chart/salaries-chart.component.ts b/src/app/modules/salaries/components/salaries-chart/salaries-chart.component.ts index e440c059..096ca514 100644 --- a/src/app/modules/salaries/components/salaries-chart/salaries-chart.component.ts +++ b/src/app/modules/salaries/components/salaries-chart/salaries-chart.component.ts @@ -16,6 +16,7 @@ import { ActivatedRouteExtended } from '@shared/routes/activated-route-extended' import { SalariesChartActivatedRoute } from './salaries-activated-route'; import { AbsoluteLink, ClipboardCopier } from '@shared/value-objects/clipboard-copier'; import { environment } from '@environments/environment'; +import { CurrentUserSalaryLabelData } from './current-user-salary-label-data'; @Component({ templateUrl: './salaries-chart.component.html', @@ -26,6 +27,7 @@ export class SalariesChartComponent implements OnInit, OnDestroy { readonly isYourSalaryWithinMarketTitle = 'Ваша зарплата «в рынке»?'; salariesChart: SalariesChart | null = null; + currentUserSalary: CurrentUserSalaryLabelData | null = null; filterData = new SalaryChartGlobalFiltersData(); readonly activatedRoute: SalariesChartActivatedRoute; @@ -64,6 +66,7 @@ export class SalariesChartComponent implements OnInit, OnDestroy { load(data: SalaryChartGlobalFiltersData | null = null): void { this.salariesChart = null; this.openAddSalaryModal = false; + this.currentUserSalary = null; this.service.charts({ grade: data?.grade ?? null, @@ -78,6 +81,10 @@ export class SalariesChartComponent implements OnInit, OnDestroy { this.salariesChart = new StubSalariesChart(x); } else { this.salariesChart = new SalariesChart(x); + this.currentUserSalary = x.currentUserSalary != null + ? new CurrentUserSalaryLabelData(x.currentUserSalary) + : null; + this.showDataStub = false; this.showAdjustCurrentSalaryProfessionModal = x.currentUserSalary != null && From 24da5ce17fe3a528e37a62d56caa5e2049e8dc7b Mon Sep 17 00:00:00 2001 From: "maxim.gorbatyuk" Date: Wed, 31 Jan 2024 19:07:01 +0600 Subject: [PATCH 5/5] test adjustment --- .../directives/numbers-only.directive.spec.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/app/shared/directives/numbers-only.directive.spec.ts b/src/app/shared/directives/numbers-only.directive.spec.ts index d9744dce..20ad3401 100644 --- a/src/app/shared/directives/numbers-only.directive.spec.ts +++ b/src/app/shared/directives/numbers-only.directive.spec.ts @@ -4,17 +4,17 @@ describe('OnlyNumberDirective', () => { it('.isNotDigit should return false if digit is passed', () => { const target = new OnlyNumberDirective(null); - expect(target.isNotDigit('1')).toEqual(false); - expect(target.isNotDigit('1213')).toEqual(false); - expect(target.isNotDigit('0234')).toEqual(false); + expect(target.isDigit('1')).toEqual(true); + expect(target.isDigit('1213')).toEqual(true); + expect(target.isDigit('0234')).toEqual(true); }); - it('.isNotDigit should return true if not-digit is passed', () => { + it('.isDigit should return false if not-digit is passed', () => { const target = new OnlyNumberDirective(null); - expect(target.isNotDigit('asd')).toEqual(true); - expect(target.isNotDigit('dfg')).toEqual(true); - expect(target.isNotDigit('!@#$%^&*()_+=-')).toEqual(true); + expect(target.isDigit('asd')).toEqual(false); + expect(target.isDigit('dfg')).toEqual(false); + expect(target.isDigit('!@#$%^&*()_+=-')).toEqual(false); }); it('.isSpecialKey should return true if allowed special key is passed', () => {