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 @@ -68,7 +68,7 @@ export class ProfileEditEducationInfoService {
}

const educationOverflow =
(this.profileForm.get("organizationName")?.value?.length ?? 0) > 100 ||
(this.profileForm.get("organizationName")?.value?.length ?? 0) > 255 ||
(this.profileForm.get("description")?.value?.length ?? 0) > 400;

if (educationOverflow) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { signal } from "@angular/core";
import { TestBed } from "@angular/core/testing";
import { FormControl, FormGroup } from "@angular/forms";
import { ActivatedRoute } from "@angular/router";
import { of } from "rxjs";
import { Skill } from "@domain/skills/skill.model";
Expand Down Expand Up @@ -148,4 +149,28 @@ describe("ProfileEditInfoService", () => {
expect(service.isModalErrorSkillChooseText()).toContain("Регион");
expect(service.isModalErrorSkillChooseText()).not.toContain("Город");
});

it("preserves legacy education names over 100 characters when saving other profile fields", () => {
const legacyName = "Название университета ".repeat(7);
profileFormService.profileId.set(42);
profileFormService.education.push(
new FormGroup({ organizationName: new FormControl(legacyName) }),
);
profileFormService.getForm().patchValue({
firstName: "Иван",
lastName: "Иванов",
birthday: "25.03.1990",
city: "Москва",
speciality: "Frontend developer",
skills: [{ id: 1, name: "Angular" }] as Skill[],
organizationName: legacyName,
});

service.saveProfile();

expect(saveProfileUseCase.execute).toHaveBeenCalledOnce();
expect(saveProfileUseCase.execute.mock.calls[0][1].education).toEqual([
{ organizationName: legacyName },
]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export class ProfileEditInfoService {
const lengthLimits: { field: string; limit: number }[] = [
{ field: "city", limit: 100 },
{ field: "aboutMe", limit: 300 },
{ field: "organizationName", limit: 100 },
{ field: "organizationName", limit: 255 },
{ field: "description", limit: 400 },
{ field: "organization", limit: 50 },
{ field: "descriptionWork", limit: 400 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export class ProfileFormService {
coverImageAddress: [null],

// education
organizationName: ["", Validators.maxLength(100)],
organizationName: ["", Validators.maxLength(255)],
entryYear: [null],
completionYear: [null],
description: [null, Validators.maxLength(400)],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/** @format */

export interface University {
id: number;
name: string;
fullName: string;
aliases: string;
city: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/** @format */

import { HttpParams } from "@angular/common/http";
import { inject, Injectable } from "@angular/core";
import { ApiService } from "@corelib";
import { ApiPagination } from "@domain/other/api-pagination.model";
import { University } from "@domain/profile/university.model";
import { Observable } from "rxjs";

@Injectable({ providedIn: "root" })
export class UniversityHttpAdapter {
private readonly api = inject(ApiService);

search(search: string, offset = 0): Observable<ApiPagination<University>> {
return this.api.get(
"/auth/universities/",
new HttpParams({ fromObject: { search, offset, limit: 30 } }),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,11 @@
<div class="profile__column">
@if (profileForm.get("organizationName"); as organizationName) {
<fieldset>
<label for="organizationName" class="field-label">название учереждения</label>
<app-input
size="big"
<label for="organizationName" class="field-label">название учреждения</label>
<app-university-select
[error]="organizationName | controlError"
placeholder="вуз / колледж / школа"
formControlName="organizationName"
[maxLength]="100"
></app-input>
></app-university-select>
@if (organizationName | controlError: "required") {
<div class="text-body-10 error">
{{ errorMessage.VALIDATION_REQUIRED }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ProfileFormService } from "@api/profile/facades/edit/profile-form.servi
import { ProfileEditEducationInfoService } from "@api/profile/facades/edit/profile-edit-education-info.service";
import { ProfileDetailUIInfoService } from "@api/profile/facades/detail/ui/profile-detail-ui-info.service";
import { TruncatePipe, ControlErrorPipe } from "@corelib";
import { UniversitySelectComponent } from "../university-select/university-select.component";

/** Шаг редактирования образования в общей форме профиля. */
@Component({
Expand All @@ -22,6 +23,7 @@ import { TruncatePipe, ControlErrorPipe } from "@corelib";
SelectComponent,
ButtonComponent,
InputComponent,
UniversitySelectComponent,
ReactiveFormsModule,
ControlErrorPipe,
TruncatePipe,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<!-- @format -->

<div class="university" (focusout)="onFocusOut($event)">
<div class="university__field">
<input
#nameInput
[id]="inputId()"
class="university__input"
[class.university__input--error]="error()"
[value]="text()"
[disabled]="disabled()"
maxlength="255"
autocomplete="off"
[placeholder]="custom() ? 'введите название учреждения' : 'выберите вуз или начните писать'"
role="combobox"
aria-autocomplete="list"
aria-haspopup="listbox"
[attr.aria-expanded]="open()"
[attr.aria-controls]="inputId() + '-list'"
[attr.aria-invalid]="error()"
[attr.aria-activedescendant]="
activeIndex() >= 0 ? inputId() + '-option-' + activeIndex() : null
"
(input)="onInput($event)"
(focus)="onFocus()"
(keydown)="onKeyDown($event)"
/>
<button
type="button"
class="university__toggle"
[disabled]="disabled()"
aria-label="Открыть список вузов"
(mousedown)="$event.preventDefault()"
(click)="browse()"
>
<i
class="university__arrow"
[class.university__arrow--open]="open()"
appIcon
icon="arrow-no-body"
appWidth="10"
appHeight="8"
aria-hidden="true"
></i>
</button>
</div>
@if (custom()) {
<p class="university__hint">Другое — укажите название учреждения</p>
}
@if (open()) {
<div class="university__dropdown">
<div class="university__status" aria-live="polite">
@if (loading()) {
Поиск вузов…
} @else if (failed()) {
Не удалось загрузить список. Попробуйте снова или выберите «Другое».
} @else if (!universities().length) {
Совпадений нет. Выберите «Другое», чтобы указать название.
}
</div>
<div [id]="inputId() + '-list'" role="listbox" aria-label="Учебные учреждения">
@for (university of universities(); track university.id; let index = $index) {
<div
[id]="inputId() + '-option-' + index"
role="option"
class="university__option"
[class.university__option--active]="activeIndex() === index"
[attr.aria-selected]="text() === university.name"
(mousedown)="$event.preventDefault()"
(click)="select(university)"
>
<span>{{ university.name }}</span>
@if (university.city) {
<small>{{ university.city }}</small>
}
</div>
}
<div
[id]="inputId() + '-option-' + universities().length"
role="option"
class="university__option university__option--other"
[class.university__option--active]="activeIndex() === universities().length"
aria-selected="false"
(mousedown)="$event.preventDefault()"
(click)="selectOther()"
>
Другое
</div>
</div>
@if (universities().length < total()) {
<button
type="button"
class="university__more"
[attr.aria-disabled]="loading()"
(click)="loadMore()"
>
Показать ещё
</button>
}
</div>
}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
@use "styles/typography";

.university {
position: relative;
width: 100%;

&__field {
position: relative;
}

&__input {
width: 100%;
padding: 12px 42px 12px 12px;
color: var(--black);
background: var(--white);
border: 0.5px solid var(--gray);
border-radius: var(--rounded-xxl);
outline: none;

@include typography.body-12;

&::placeholder {
color: var(--dark-grey);
}

&:focus {
border-color: var(--accent);
box-shadow: 0 0 6px rgb(109 40 255 / 30%);
}

&--error {
border-color: var(--red);
}
}

&__toggle {
position: absolute;
top: 0;
right: 0;
display: flex;
align-items: center;
justify-content: flex-end;
width: 44px;
height: 100%;
padding: 0 12px;
color: var(--dark-grey);
cursor: pointer;
background: transparent;
border: none;
}

&__arrow {
flex-shrink: 0;
color: var(--dark-grey);
transition:
transform 0.2s,
color 0.2s;
transform: rotate(180deg);

&--open {
color: var(--accent);
transform: rotate(0deg);
}
}

&__dropdown {
position: absolute;
right: 0;
left: 0;
z-index: 20;
max-height: 300px;
padding: 6px;
margin-top: 4px;
overflow-y: auto;
background: var(--white);
border: 0.5px solid var(--gray);
border-radius: var(--rounded-lg);
box-shadow: 0 5px 20px rgb(0 0 0 / 10%);

@include typography.body-12;
}

&__option {
padding: 10px;
overflow-wrap: anywhere;
cursor: pointer;
border-radius: var(--rounded-lg);

&:hover,
&--active {
background: var(--light-gray);
}

small {
display: block;
margin-top: 3px;
color: var(--grey-for-text);
}

&--other {
color: var(--accent);
}
}

&__hint,
&__status {
color: var(--grey-for-text);

@include typography.body-10;
}

&__hint {
margin-top: 6px;
}

&__status:not(:empty) {
padding: 6px 10px;
}

&__more {
width: 100%;
padding: 10px;
color: var(--accent);
cursor: pointer;
background: transparent;
border: none;
}
}
Loading