diff --git a/projects/skills/src/app/rating/general/general.component.ts b/projects/skills/src/app/rating/general/general.component.ts index 5b08a4f45..c0e50fe6e 100644 --- a/projects/skills/src/app/rating/general/general.component.ts +++ b/projects/skills/src/app/rating/general/general.component.ts @@ -11,7 +11,7 @@ import { SelectComponent } from "@ui/components"; import { IconComponent } from "@uilib"; import { FormBuilder, type FormGroup, ReactiveFormsModule } from "@angular/forms"; import { RatingService } from "../services/rating.service"; -import { ratingFiltersList } from "projects/core/src/consts/filters/rating-filter.const"; +import { ratingFilters } from "projects/core/src/consts/filters/rating-filter.const"; /** * Компонент общего рейтинга пользователей @@ -66,7 +66,7 @@ export class RatingGeneralComponent implements OnInit { ratingForm: FormGroup; // Константы фильтров из конфигурации - readonly filterParams = ratingFiltersList; + readonly filterParams = ratingFilters; /** * Инициализация компонента diff --git a/projects/social_platform/src/app/auth/auth.component.scss b/projects/social_platform/src/app/auth/auth.component.scss index dbdde5e20..08453bf94 100644 --- a/projects/social_platform/src/app/auth/auth.component.scss +++ b/projects/social_platform/src/app/auth/auth.component.scss @@ -10,7 +10,7 @@ padding: 0 15px; @include responsive.apply-desktop { - padding: 0 100px; + padding: 0 200px; } &__header { diff --git a/projects/social_platform/src/app/auth/email-verification/email-verification.component.html b/projects/social_platform/src/app/auth/email-verification/email-verification.component.html index 098f19aef..50e0cb484 100644 --- a/projects/social_platform/src/app/auth/email-verification/email-verification.component.html +++ b/projects/social_platform/src/app/auth/email-verification/email-verification.component.html @@ -1,7 +1,7 @@
- +
@@ -22,6 +22,6 @@

Мы отправили ссылку подтвержд

}
- email + email
diff --git a/projects/social_platform/src/app/auth/email-verification/email-verification.component.scss b/projects/social_platform/src/app/auth/email-verification/email-verification.component.scss index 964c8cc81..c79e6ee7a 100644 --- a/projects/social_platform/src/app/auth/email-verification/email-verification.component.scss +++ b/projects/social_platform/src/app/auth/email-verification/email-verification.component.scss @@ -23,17 +23,21 @@ @include responsive.apply-desktop { flex-direction: row; - justify-content: space-between; } } &__img { - width: 335px; - margin-top: 55px; + position: fixed; + top: 8%; + right: 0%; + bottom: 0%; + left: -33%; + min-width: 500px; @include responsive.apply-desktop { - width: 470px; - margin-top: 0; + bottom: 0%; + left: 30%; + min-width: 852px; } } diff --git a/projects/social_platform/src/app/auth/login/login.component.html b/projects/social_platform/src/app/auth/login/login.component.html index d7916f0a0..6076861c9 100644 --- a/projects/social_platform/src/app/auth/login/login.component.html +++ b/projects/social_platform/src/app/auth/login/login.component.html @@ -1,95 +1,141 @@ -
-
-

Вход

- - @if (loginForm.get("email"); as email) { -
- - - @if (email | controlError: "email") { -
- {{ errorMessage.VALIDATION_EMAIL }} -
- } @if (email | controlError: "required") { -
- {{ errorMessage.VALIDATION_REQUIRED }} -
- } -
- } @if (loginForm.get("password"); as password) { -
- - - @if(showPassword) { - - } @else { - +
- } @if (errorWrongAuth) { -
- {{ errorMessage.AUTH_WRONG_AUTH }}
- } - - Войти - -
- + +
+ +
+ diff --git a/projects/social_platform/src/app/auth/login/login.component.scss b/projects/social_platform/src/app/auth/login/login.component.scss index 7c15ef20f..67dddf0c4 100644 --- a/projects/social_platform/src/app/auth/login/login.component.scss +++ b/projects/social_platform/src/app/auth/login/login.component.scss @@ -4,26 +4,56 @@ @use "styles/typography"; .login { - &__reg { - margin-bottom: 30px; + display: flex; + min-width: 95vw; + min-height: 100vh; - @include typography.body-14; + &__left { + display: flex; + flex: 1; + flex-direction: column; + padding-left: 10px; + } + + &__left-content { + display: flex; + flex: 1; + align-items: center; + } + + &__left-footer { + padding-bottom: 24px; + } + + &__right { + display: none; @include responsive.apply-desktop { - @include typography.body-16; + display: block; + width: 50%; } } - &__forget { - display: block; - text-align: center; + &__image { + width: 100%; + height: 100%; + object-fit: cover; + } - @include typography.body-12; + &__additional { + display: flex; + flex-direction: column; + gap: 8px; + margin-top: 7px; + margin-left: 18px; + } - @include responsive.apply-desktop { - text-align: left; + &_external { + width: 80%; + color: var(--grey-for-text); - @include typography.body-14; + @include responsive.apply-desktop { + width: 100%; } } @@ -33,6 +63,54 @@ justify-content: space-between; margin-top: 12px; } + + &__forget { + @include typography.body-12; + } + + .auth { + padding: 0; + + &__wrapper { + max-width: 333px; + } + } + + &__logo { + display: block; + width: 95px; + margin-bottom: 20px; + + @include responsive.apply-desktop { + width: 157px; + height: 30px; + } + } +} + +.tooltip { + position: relative; + display: flex; + align-items: center; + + &__wrapper { + position: absolute; + bottom: 22px; + left: 38px; + width: 260px; + padding: 18px 10px 10px 16px; + background-color: var(--white); + border: 0.5px solid var(--grey-for-text); + border-radius: var(--rounded-lg) var(--rounded-lg) var(--rounded-lg) 0; + } + + &__text { + color: var(--grey-for-text); + + a { + color: var(--accent); + } + } } .icon { diff --git a/projects/social_platform/src/app/auth/login/login.component.ts b/projects/social_platform/src/app/auth/login/login.component.ts index fa3ab6826..751f4d213 100644 --- a/projects/social_platform/src/app/auth/login/login.component.ts +++ b/projects/social_platform/src/app/auth/login/login.component.ts @@ -1,6 +1,12 @@ /** @format */ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + OnInit, + signal, +} from "@angular/core"; import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms"; import { AuthService } from "../services"; import { ErrorMessage } from "@error/models/error-message"; @@ -8,6 +14,8 @@ import { ControlErrorPipe, TokenService, ValidationService } from "projects/core import { ActivatedRoute, Router, RouterLink } from "@angular/router"; import { ButtonComponent, IconComponent, InputComponent } from "@ui/components"; import { CommonModule } from "@angular/common"; +import { TooltipComponent } from "@ui/components/tooltip/tooltip.component"; +import { ClickOutsideModule } from "ng-click-outside"; /** * Компонент входа в систему @@ -40,6 +48,8 @@ import { CommonModule } from "@angular/common"; ButtonComponent, IconComponent, ControlErrorPipe, + TooltipComponent, + ClickOutsideModule, ], }) export class LoginComponent implements OnInit { @@ -66,11 +76,16 @@ export class LoginComponent implements OnInit { errorMessage = ErrorMessage; showPassword = false; + readonly isHintLoginVisible = signal(false); ngOnInit(): void { this.tokenService.clearTokens(); } + toggleTooltip(): void { + this.isHintLoginVisible.set(!this.isHintLoginVisible()); + } + toggleShowPassword() { this.showPassword = !this.showPassword; } diff --git a/projects/social_platform/src/app/auth/register/register.component.html b/projects/social_platform/src/app/auth/register/register.component.html index aff9c2b9f..f3ea50f77 100644 --- a/projects/social_platform/src/app/auth/register/register.component.html +++ b/projects/social_platform/src/app/auth/register/register.component.html @@ -2,185 +2,23 @@
-

- @if (step === "credentials") { - - Приветствуем на Procollab! 👋
- Строй свою карьеру уже сегодня 🚀 -
- } @else if (step === "info") { - - PROCOLLAB — твой путь к первой работе, давай начнем создавать твое первое резюме прямо - сейчас - - } -

-

- @if (step === "credentials") { - Для регистрации введите данные - } @else if (step === "info") { - - - - } -

+
-
- @if (step === "credentials") { - - @if (registerForm.get("email"); as email) { -
- - - @if (credsSubmitInitiated) { - - @if (email | controlError: "required") { -
- {{ errorMessage.VALIDATION_REQUIRED }} -
- } @if (email | controlError: "email") { -
- {{ errorMessage.VALIDATION_EMAIL }} -
- } -
- } -
- } @if (registerForm.get("password"); as password) { -
- - - @if(showPassword) { - - } @else { - - } - - - @if(showPasswordRepeat) { - - } @else { - - } - - @if (credsSubmitInitiated) { - - @if (password | controlError: "required") { -
- {{ errorMessage.VALIDATION_REQUIRED }} -
- } @if (password | controlError: "passwordTooShort") { -
- @if (password.errors) { Пароль должен содержать минимум - {{ password.errors["passwordTooShort"]["requiredLength"] }} символов } -
- } @if (password | controlError: "passwordNoUppercase") { -
- Пароль должен содержать минимум одну заглавную букву (A-Z) -
- } @if (password | controlError: "passwordNoLowercase") { -
- Пароль должен содержать минимум одну строчную букву (a-z) -
- } @if (password | controlError: "passwordNoNumber") { -
Пароль должен содержать минимум одну цифру (0-9)
- } @if (password | controlError: "passwordNoSpecialChar") { -
- Пароль должен содержать минимум один специальный символ -
- } @if (password | controlError: "passwordHasSpaces") { -
Пароль не должен содержать пробелы
- } @if (password | controlError: "passwordHasSequence") { -
- Пароль не должен содержать последовательности символов (123456, abcdef и т.д.) -
- } @if (password | controlError: "passwordHasRepeating") { -
- Пароль не должен содержать более 2 одинаковых символов подряд -
- } @if (password | controlError: "unMatch") { -
- {{ errorMessage.VALIDATION_PASSWORD_UNMATCH }} -
- } -
- } -
- } -
- } @else if (step === "info") { +
@if (registerForm.get("firstName"); as name) {
- + - @if (infoSubmitInitiated) { + @if ((name | controlError) && registerIsSubmitting) { @if (name | controlError: "required") {
@@ -196,16 +34,16 @@

} @if (registerForm.get("lastName"); as surname) {
- + - @if (infoSubmitInitiated) { + @if ((surname | controlError) && registerIsSubmitting) { @if (surname | controlError: "required") {
@@ -221,19 +59,20 @@

}
+ @if (registerForm.get("birthday"); as birthday) {
- + - @if (infoSubmitInitiated) { + @if ((birthday | controlError) && registerIsSubmitting) { @if (birthday | controlError: "required") {
@@ -259,56 +98,204 @@

}

- } + } @if (registerForm.get("phoneNumber"); as phoneNumber) { +
+ + + + @if (phoneNumber | controlError) { + + @if (phoneNumber | controlError: "required") { +
+ {{ errorMessage.VALIDATION_REQUIRED }} +
+ } @if (phoneNumber | controlError: "pattern") { +
Неверный формат телефона
+ } +
+ } +
+ } @if (registerForm.get("email"); as email) { +
+ + + @if ((email | controlError) && registerIsSubmitting) { + + @if (email | controlError: "required") { +
+ {{ errorMessage.VALIDATION_REQUIRED }} +
+ } @if (email | controlError: "email") { +
+ {{ errorMessage.VALIDATION_EMAIL }} +
+ } +
+ } +
+ } @if (registerForm.get("password"); as password) { +
+ + + @if(showPassword) { + + } @else { + + } + +
+ +
+ + + @if(showPasswordRepeat) { + + } @else { + + } + +
+ + @if ((password | controlError)) { + + @if (password | controlError: "required") { +
+ {{ errorMessage.VALIDATION_REQUIRED }} +
+ } @if (password | controlError: "passwordTooShort") { +
+ @if (password.errors) { Пароль должен содержать минимум + {{ password.errors["passwordTooShort"]["requiredLength"] }} символов } +
+ } @if (password | controlError: "passwordNoUppercase") { +
+ Пароль должен содержать минимум одну заглавную букву (A-Z) +
+ } @if (password | controlError: "passwordNoLowercase") { +
+ Пароль должен содержать минимум одну строчную букву (a-z) +
+ } @if (password | controlError: "passwordNoNumber") { +
Пароль должен содержать минимум одну цифру (0-9)
+ } @if (password | controlError: "passwordNoSpecialChar") { +
+ Пароль должен содержать минимум один специальный символ +
+ } @if (password | controlError: "passwordHasSpaces") { +
Пароль не должен содержать пробелы
+ } @if (password | controlError: "passwordHasSequence") { +
+ Пароль не должен содержать последовательности символов (123456, abcdef и т.д.) +
+ } @if (password | controlError: "passwordHasRepeating") { +
+ Пароль не должен содержать более 2 одинаковых символов подряд +
+ } @if (password | controlError: "unMatch") { +
+ {{ errorMessage.VALIDATION_PASSWORD_UNMATCH }} +
+ } +
+ } }
- } @if (serverErrors) { + + @if (serverErrors) {
@for (e of serverErrors; track $index) {

{{ e }}

}
- } @if (step === "credentials") { + } +
- Я прочитал - - соглашение - - и даю согласие на обработку персональных данных + я прочитал соглашение и даю согласие на + обработку персональных данных
Нажимая на кнопку подтверждаете, что вам больше 14 летнажимая на кнопку подтверждаете, что вам больше 14 лет
+ - Далее - - } @else if (step === "info") { - - Создать аккаунт + зарегистрироваться - } -

Есть аккаунт? Войдите

+ +

если есть аккаунт? - войдите

{ - field?.markAsTouched(); - return !!field?.valid; - }); - - if (errors.every(Boolean) && this.registerAgreement && this.ageAgreement) { - this.step = "info"; - } - } - onSendForm(): void { if (!this.validationService.getFormValidation(this.registerForm)) { return; } - const form = { + const payload = { ...this.registerForm.value, birthday: this.registerForm.value.birthday ? dayjs(this.registerForm.value.birthday, "DD.MM.YYYY").format("YYYY-MM-DD") : undefined, + phoneNumber: + typeof this.registerForm.value.phoneNumber === "string" + ? this.registerForm.value.phoneNumber.replace(/^([87])/, "+7") + : this.registerForm.value.phoneNumber, }; - delete form.repeatedPassword; + + delete payload.repeatedPassword; this.registerIsSubmitting = true; - this.authService.register(form).subscribe({ + this.authService.register(payload).subscribe({ next: () => { this.registerIsSubmitting = false; this.cdref.detectChanges(); this.router - .navigateByUrl("/auth/verification/email?adress=" + form.email) + .navigateByUrl("/auth/verification/email?adress=" + payload.email) .then(() => console.debug("Route changed from RegisterComponent")); }, error: error => { @@ -157,7 +142,6 @@ export class RegisterComponent implements OnInit { error.status === 400 && error.error.email.some((msg: string) => msg.includes("email")) ) { - // console.log(error); this.serverErrors = Object.values(error.error).flat() as string[]; console.log(this.serverErrors); } else if (error.status === 500) { @@ -169,14 +153,4 @@ export class RegisterComponent implements OnInit { }, }); } - - onSubmit() { - if (this.step === "credentials") { - this.credsSubmitInitiated = true; - this.onInfoStep(); - } else if (this.step === "info") { - this.infoSubmitInitiated = true; - this.onSendForm(); - } - } } diff --git a/projects/social_platform/src/app/office/features/detail/detail.component.html b/projects/social_platform/src/app/office/features/detail/detail.component.html index 20dd26646..5fd27e0c4 100644 --- a/projects/social_platform/src/app/office/features/detail/detail.component.html +++ b/projects/social_platform/src/app/office/features/detail/detail.component.html @@ -86,10 +86,10 @@ } } @if (isUserMember && !isUserManager && !isUserExpert) { {{ isProjectAssigned ? "вы подали проект" : "подать проект" }} @@ -195,7 +195,7 @@ } } - + - @@ -290,6 +290,27 @@ вперед
+ --> + + +
+
+ +

произошла ошибка при редактировании!

+
+

{{ assignProjectToProgramModalMessage() }}.

+
+ + +
+
+

к сожалению, подача проекта уже завершена!

+
+ + +
+
diff --git a/projects/social_platform/src/app/office/features/detail/detail.component.ts b/projects/social_platform/src/app/office/features/detail/detail.component.ts index 58b9c3e42..c24d0ee40 100644 --- a/projects/social_platform/src/app/office/features/detail/detail.component.ts +++ b/projects/social_platform/src/app/office/features/detail/detail.component.ts @@ -14,8 +14,6 @@ import { User } from "@auth/models/user.model"; import { Collaborator } from "@office/models/collaborator.model"; import { ProjectService } from "@office/services/project.service"; import { Project } from "@office/models/project.model"; -import { HttpErrorResponse } from "@angular/common/http"; -import { ProjectAssign } from "@office/projects/models/project-assign.model"; import { ProjectAdditionalService } from "@office/projects/edit/services/project-additional.service"; import { ProjectDataService } from "@office/projects/detail/services/project-data.service"; import { ProgramDataService } from "@office/program/services/program-data.service"; @@ -27,6 +25,13 @@ import { SnackbarService } from "@ui/services/snackbar.service"; import { ApproveSkillComponent } from "../approve-skill/approve-skill.component"; import { ProjectsService } from "@office/projects/services/projects.service"; import { TruncatePipe } from "projects/core/src/lib/pipes/truncate.pipe"; +import { ProgramService } from "@office/program/services/program.service"; +import { ProjectFormService } from "@office/projects/edit/services/project-form.service"; +import { + PartnerProgramFields, + projectNewAdditionalProgramVields, +} from "@office/models/partner-program-fields.model"; +import { HttpRequest, HttpResponse } from "@angular/common/http"; @Component({ selector: "app-detail", @@ -62,6 +67,8 @@ export class DeatilComponent implements OnInit, OnDestroy { private readonly projectsService = inject(ProjectsService); public readonly chatService = inject(ChatService); private readonly cdRef = inject(ChangeDetectorRef); + private readonly programService = inject(ProgramService); + private readonly projectFormService = inject(ProjectFormService); // Основные данные(типы данных, данные) info = signal(undefined); @@ -85,6 +92,7 @@ export class DeatilComponent implements OnInit, OnDestroy { // Сторонние переменные для работы с роутингом или доп проверок backPath?: string; registerDateExpired?: boolean; + submissionProjectDateExpired?: boolean; isInProject?: boolean; isSended = false; @@ -92,20 +100,23 @@ export class DeatilComponent implements OnInit, OnDestroy { isProfileFill = false; // Переменные для работы с модалкой подачи проекта - selectedProjectId: number | null = null; - dubplicatedProjectId = 0; + // selectedProjectId: number | null = null; + // dubplicatedProjectId = 0; memberProjects: Project[] = []; userType = signal(undefined); // Сигналы для работы с модальными окнами с текстом - assignProjectToProgramModalMessage = signal(null); + // assignProjectToProgramModalMessage = signal(null); errorMessageModal = signal(""); + additionalFields = signal([]); + // Переменные для работы с модалками isAssignProjectToProgramModalOpen = signal(false); showSubmitProjectModal = signal(false); isProgramEndedModalOpen = signal(false); + isProgramSubmissionProjectsEndedModalOpen = signal(false); isLeaveProjectModalOpen = false; // Флаг модального окна выхода isEditDisable = false; // Флаг недоступности редактирования isEditDisableModal = false; // Флаг недоступности редактирования для модалки @@ -117,8 +128,15 @@ export class DeatilComponent implements OnInit, OnDestroy { showApproveSkillModal = false; readAllModal = false; + // Сигналы для работы с модальными окнами с текстом + assignProjectToProgramModalMessage = signal(null); + subscriptions: Subscription[] = []; + get projectForm() { + return this.projectFormService.formModel; + } + ngOnInit(): void { const listTypeSub$ = this.route.data.subscribe(data => { this.listType = data["listType"]; @@ -174,13 +192,13 @@ export class DeatilComponent implements OnInit, OnDestroy { /** * Переключатель для модалки выбора проекта */ - toggleSubmitProjectModal(): void { - this.showSubmitProjectModal.set(!this.showSubmitProjectModal()); + // toggleSubmitProjectModal(): void { + // this.showSubmitProjectModal.set(!this.showSubmitProjectModal()); - if (!this.showSubmitProjectModal()) { - this.selectedProjectId = null; - } - } + // if (!this.showSubmitProjectModal()) { + // this.selectedProjectId = null; + // } + // } /** Показать подсказку */ showTooltip(): void { @@ -195,41 +213,68 @@ export class DeatilComponent implements OnInit, OnDestroy { /** * Обработчик изменения радио-кнопки для выбора проекта */ - onProjectRadioChange(event: Event): void { - const target = event.target as HTMLInputElement; - this.selectedProjectId = +target.value; + // onProjectRadioChange(event: Event): void { + // const target = event.target as HTMLInputElement; + // this.selectedProjectId = +target.value; - if (this.selectedProjectId) { - this.memberProjects.find(project => project.id === this.selectedProjectId); - } - } + // if (this.selectedProjectId) { + // this.memberProjects.find(project => project.id === this.selectedProjectId); + // } + // } /** * Добавление проекта на программу */ - addProjectModal(): void { - if (this.selectedProjectId === null) { - return; - } + // addProjectModal(): void { + // if (this.selectedProjectId === null) { + // return; + // } - const selectedProject = this.memberProjects.find( - project => project.id === this.selectedProjectId - ); + // const selectedProject = this.memberProjects.find( + // project => project.id === this.selectedProjectId + // ); - if (!selectedProject) { - this.snackbarService.error("Проект не найден. Попробуйте выбрать другой проект."); - return; - } + // if (!selectedProject) { + // this.snackbarService.error("Проект не найден. Попробуйте выбрать другой проект."); + // return; + // } - this.assignProjectToProgram(selectedProject!); - } + // this.assignProjectToProgram(selectedProject!); + // } - get isProjectSelected(): boolean { - return this.selectedProjectId !== null; - } + // get isProjectSelected(): boolean { + // return this.selectedProjectId !== null; + // } addNewProject(): void { - this.projectsService.addProject(); + const newFieldsFormValues: projectNewAdditionalProgramVields[] = []; + + this.additionalFields().forEach((field: PartnerProgramFields) => { + newFieldsFormValues.push({ + field_id: field.id, + value_text: field.options.length ? field.options[0] : "'", + }); + }); + + const body = { project: this.projectForm.value, program_field_values: newFieldsFormValues }; + + this.programService.applyProjectToProgram(this.info().id, body).subscribe({ + next: r => { + this.router + .navigate([`/office/projects/${r.projectId}/edit`], { + queryParams: { editingStep: "main", fromProgram: true }, + }) + .then(() => console.debug("Route change from ProjectsComponent")); + }, + error: err => { + if (err) { + if (err.status === 400) { + this.isAssignProjectToProgramModalOpen.set(true); + this.assignProjectToProgramModalMessage.set(err.error.detail); + } + } + }, + }); } /** Эмитим логику для привязки проекта к программе */ @@ -237,41 +282,41 @@ export class DeatilComponent implements OnInit, OnDestroy { * Привязка проекта к программе выбранной * Перенаправление её на редактирование "нового" проекта */ - assignProjectToProgram(project: Project): void { - if (!project || !project.id) { - this.snackbarService.error("Ошибка при выборе проекта"); - return; - } - - if (this.info().id) { - this.projectService - .assignProjectToProgram(project.id, Number(this.route.snapshot.params["programId"])) - .subscribe({ - next: r => { - this.dubplicatedProjectId = r.newProjectId; - this.assignProjectToProgramModalMessage.set(r); - this.isAssignProjectToProgramModalOpen.set(true); - this.toggleSubmitProjectModal(); - this.selectedProjectId = null; - }, - - error: err => { - if (err instanceof HttpErrorResponse) { - if (err.status === 400) { - this.setAssignProjectToProgramError(err.error); - } - } - }, - }); - } - } - - closeAssignProjectToProgramModal(): void { - this.isAssignProjectToProgramModalOpen.set(false); - this.router.navigateByUrl( - `/office/projects/${this.dubplicatedProjectId}/edit?editingStep=main` - ); - } + // assignProjectToProgram(project: Project): void { + // if (!project || !project.id) { + // this.snackbarService.error("Ошибка при выборе проекта"); + // return; + // } + + // if (this.info().id) { + // this.projectService + // .assignProjectToProgram(project.id, Number(this.route.snapshot.params["programId"])) + // .subscribe({ + // next: r => { + // this.dubplicatedProjectId = r.newProjectId; + // this.assignProjectToProgramModalMessage.set(r); + // this.isAssignProjectToProgramModalOpen.set(true); + // this.toggleSubmitProjectModal(); + // this.selectedProjectId = null; + // }, + // + // error: err => { + // if (err instanceof HttpErrorResponse) { + // if (err.status === 400) { + // this.setAssignProjectToProgramError(err.error); + // } + // } + // }, + // }); + // } + // } + + // closeAssignProjectToProgramModal(): void { + // this.isAssignProjectToProgramModalOpen.set(false); + // this.router.navigateByUrl( + // `/office/projects/${this.dubplicatedProjectId}/edit?editingStep=main` + // ); + // } /** * Закрытие модального окна выхода из проекта @@ -389,6 +434,13 @@ export class DeatilComponent implements OnInit, OnDestroy { event.preventDefault(); event.stopPropagation(); this.isProgramEndedModalOpen.set(true); + } else if ( + program?.datetimeProjectSubmissionEnds && + Date.now() > Date.parse(program?.datetimeProjectSubmissionEnds) + ) { + event.preventDefault(); + event.stopPropagation(); + this.isProgramSubmissionProjectsEndedModalOpen.set(true); } else { this.router.navigateByUrl("/office/program/" + this.info().id + "/register"); } @@ -434,7 +486,10 @@ export class DeatilComponent implements OnInit, OnDestroy { tap(program => { if (program) { this.info.set(program); + this.loadAdditionalFields(program.id); this.registerDateExpired = Date.now() > Date.parse(program.datetimeRegistrationEnds); + this.submissionProjectDateExpired = + Date.now() > Date.parse(program.datetimeProjectSubmissionEnds); } }) ) @@ -504,4 +559,18 @@ export class DeatilComponent implements OnInit, OnDestroy { this.backPath = "/office/program/all"; } } + + private loadAdditionalFields(programId: number): void { + const additionalFieldsSub$ = this.programService + .getProgramProjectAdditionalFields(programId) + .subscribe({ + next: ({ programFields }) => { + if (programFields) { + this.additionalFields.set(programFields); + } + }, + }); + + this.subscriptions.push(additionalFieldsSub$); + } } diff --git a/projects/social_platform/src/app/office/features/info-card/info-card.component.scss b/projects/social_platform/src/app/office/features/info-card/info-card.component.scss index 747be4904..d721a619d 100644 --- a/projects/social_platform/src/app/office/features/info-card/info-card.component.scss +++ b/projects/social_platform/src/app/office/features/info-card/info-card.component.scss @@ -61,6 +61,7 @@ position: absolute; top: 63%; left: 30%; + z-index: 10000; padding: 3px 5px; background-color: var(--light-white); border: 0.5px solid var(--medium-grey-for-outline); diff --git a/projects/social_platform/src/app/office/features/program-sidebar-card/program-sidebar-card.component.html b/projects/social_platform/src/app/office/features/program-sidebar-card/program-sidebar-card.component.html new file mode 100644 index 000000000..77b88001a --- /dev/null +++ b/projects/social_platform/src/app/office/features/program-sidebar-card/program-sidebar-card.component.html @@ -0,0 +1,22 @@ + + +
+
+
+ +

до {{ program.datetimeRegistrationEnds | date: "dd.MM.yy" }}

+
+ + +
+ +

+ {{ program.name | truncate: 30 }} +

+
diff --git a/projects/social_platform/src/app/office/features/program-sidebar-card/program-sidebar-card.component.scss b/projects/social_platform/src/app/office/features/program-sidebar-card/program-sidebar-card.component.scss new file mode 100644 index 000000000..90bac9eab --- /dev/null +++ b/projects/social_platform/src/app/office/features/program-sidebar-card/program-sidebar-card.component.scss @@ -0,0 +1,31 @@ +.program { + display: flex; + flex-direction: column; + gap: 5px; + padding: 8px; + cursor: pointer; + background-color: var(--light-white); + border: 0.5px solid var(--medium-grey-for-outline); + border-radius: var(--rounded-lg); + transition: background-color 0.2s ease-in-out; + + &:hover { + background-color: var(--white); + } + + &__top { + display: flex; + align-items: center; + justify-content: space-between; + } + + &__info { + display: flex; + gap: 5px; + align-items: center; + } + + &__title { + color: var(--black); + } +} diff --git a/projects/social_platform/src/app/office/features/program-sidebar-card/program-sidebar-card.component.ts b/projects/social_platform/src/app/office/features/program-sidebar-card/program-sidebar-card.component.ts new file mode 100644 index 000000000..233151650 --- /dev/null +++ b/projects/social_platform/src/app/office/features/program-sidebar-card/program-sidebar-card.component.ts @@ -0,0 +1,19 @@ +/** @format */ + +import { CommonModule } from "@angular/common"; +import { Component, Input } from "@angular/core"; +import { IconComponent } from "@uilib"; +import { AvatarComponent } from "@ui/components/avatar/avatar.component"; +import { TruncatePipe } from "projects/core/src/lib/pipes/truncate.pipe"; +import { Program } from "@office/program/models/program.model"; + +@Component({ + selector: "app-program-sidebar-card", + templateUrl: "./program-sidebar-card.component.html", + styleUrl: "./program-sidebar-card.component.scss", + imports: [CommonModule, IconComponent, AvatarComponent, TruncatePipe], + standalone: true, +}) +export class ProgramSidebarCardComponent { + @Input() program!: Program; +} diff --git a/projects/social_platform/src/app/office/features/project-rating/project-rating.component.html b/projects/social_platform/src/app/office/features/project-rating/project-rating.component.html index 7b4709ee0..3882f9ee2 100644 --- a/projects/social_platform/src/app/office/features/project-rating/project-rating.component.html +++ b/projects/social_platform/src/app/office/features/project-rating/project-rating.component.html @@ -30,6 +30,14 @@ [formControlName]="criterion.id" placeholder="Небольшой комментарий, если необходимо" > + + @if (form.get(criterion.id.toString())?.errors?.['maxlength']) { +
+ {{ errorMessage.VALIDATION_TOO_LONG }} + максимум + {{ form.get(criterion.id.toString())?.errors?.['maxlength']?.['requiredLength'] }} символов +
+ } } } diff --git a/projects/social_platform/src/app/office/features/project-rating/project-rating.component.ts b/projects/social_platform/src/app/office/features/project-rating/project-rating.component.ts index ab008d295..8a6dcfe42 100644 --- a/projects/social_platform/src/app/office/features/project-rating/project-rating.component.ts +++ b/projects/social_platform/src/app/office/features/project-rating/project-rating.component.ts @@ -27,6 +27,7 @@ import { noop, Subscription } from "rxjs"; import { BooleanCriterionComponent } from "./components/boolean-criterion/boolean-criterion.component"; import { RangeCriterionInputComponent } from "./components/range-criterion-input/range-criterion-input.component"; import { ErrorMessage } from "@error/models/error-message"; +import { ControlErrorPipe } from "@corelib"; /** * Компонент рейтинга проекта @@ -48,6 +49,7 @@ import { ErrorMessage } from "@error/models/error-message"; RangeCriterionInputComponent, BooleanCriterionComponent, ReactiveFormsModule, + ControlErrorPipe, ], templateUrl: "./project-rating.component.html", styleUrl: "./project-rating.component.scss", @@ -110,6 +112,8 @@ export class ProjectRatingComponent implements OnDestroy, ControlValueAccessor, /** Форма для управления всеми критериями оценки */ form!: FormGroup; + errorMessage = ErrorMessage; + /** * Объект с функциями-создателями FormControl для разных типов критериев * Каждый тип критерия имеет свою логику создания контрола и валидации @@ -120,7 +124,7 @@ export class ProjectRatingComponent implements OnDestroy, ControlValueAccessor, // Булевый критерий - преобразование строки в boolean bool: val => new FormControl(val ? JSON.parse((val as string).toLowerCase()) : false), // Строковый критерий - без валидации (комментарии опциональны) - str: val => new FormControl(val), + str: val => new FormControl(val, Validators.maxLength(50)), }; /** Сигнал для хранения подписок */ diff --git a/projects/social_platform/src/app/office/office.component.html b/projects/social_platform/src/app/office/office.component.html index 8d0c98b96..984883b9e 100644 --- a/projects/social_platform/src/app/office/office.component.html +++ b/projects/social_platform/src/app/office/office.component.html @@ -25,6 +25,15 @@

Политика обработки персональных данных

2022

+ +
+ @if (programs().length) { @for (program of programs(); track program.id) { + + } } +
diff --git a/projects/social_platform/src/app/office/office.component.scss b/projects/social_platform/src/app/office/office.component.scss index 9738a8301..808ce9de9 100644 --- a/projects/social_platform/src/app/office/office.component.scss +++ b/projects/social_platform/src/app/office/office.component.scss @@ -56,6 +56,14 @@ } } + &__programs { + display: flex; + flex-direction: column; + gap: 12px; + max-width: 135px; + margin-top: 12px; + } + &__inner { display: flex; width: 100%; @@ -89,7 +97,7 @@ display: flex; flex-direction: column; gap: 2px; - width: 70%; + width: 80%; margin-top: 30px; color: var(--dark-grey); } diff --git a/projects/social_platform/src/app/office/office.component.ts b/projects/social_platform/src/app/office/office.component.ts index 3c82830dd..ad397fc50 100644 --- a/projects/social_platform/src/app/office/office.component.ts +++ b/projects/social_platform/src/app/office/office.component.ts @@ -1,12 +1,11 @@ /** @format */ -import { Component, OnDestroy, OnInit, Signal } from "@angular/core"; +import { Component, OnDestroy, OnInit, signal, Signal } from "@angular/core"; import { IndustryService } from "@services/industry.service"; import { forkJoin, map, noop, Subscription } from "rxjs"; -import { ActivatedRoute, Router, RouterOutlet } from "@angular/router"; +import { ActivatedRoute, Router, RouterOutlet, RouterLink } from "@angular/router"; import { Invite } from "@models/invite.model"; import { AuthService } from "@auth/services"; -import { ProjectService } from "@services/project.service"; import { User } from "@auth/models/user.model"; import { ChatService } from "@services/chat.service"; import { SnackbarComponent } from "@ui/components/snackbar/snackbar.component"; @@ -14,10 +13,13 @@ import { DeleteConfirmComponent } from "@ui/components/delete-confirm/delete-con import { ButtonComponent } from "@ui/components"; import { ModalComponent } from "@ui/components/modal/modal.component"; import { NavComponent } from "./features/nav/nav.component"; -import { IconComponent, ProfileControlPanelComponent, SidebarComponent } from "@uilib"; +import { ProfileControlPanelComponent, SidebarComponent } from "@uilib"; import { AsyncPipe } from "@angular/common"; import { InviteService } from "@services/invite.service"; import { toSignal } from "@angular/core/rxjs-interop"; +import { ProgramSidebarCardComponent } from "./features/program-sidebar-card/program-sidebar-card.component"; +import { ProgramService } from "./program/services/program.service"; +import { Program } from "./program/models/program.model"; /** * Главный компонент офиса - корневой компонент рабочего пространства @@ -45,8 +47,9 @@ import { toSignal } from "@angular/core/rxjs-interop"; DeleteConfirmComponent, SnackbarComponent, AsyncPipe, + RouterLink, ProfileControlPanelComponent, - IconComponent, + ProgramSidebarCardComponent, ], }) export class OfficeComponent implements OnInit, OnDestroy { @@ -56,7 +59,8 @@ export class OfficeComponent implements OnInit, OnDestroy { public readonly authService: AuthService, private readonly inviteService: InviteService, private readonly router: Router, - public readonly chatService: ChatService + public readonly chatService: ChatService, + private readonly programService: ProgramService ) {} invites: Signal = toSignal( @@ -72,6 +76,7 @@ export class OfficeComponent implements OnInit, OnDestroy { waitVerificationAccepted = false; inviteErrorModal = false; + protected readonly programs = signal([]); navItems: { name: string; @@ -121,6 +126,17 @@ export class OfficeComponent implements OnInit, OnDestroy { if (localStorage.getItem("waitVerificationAccepted") === "true") { this.waitVerificationAccepted = true; } + + const programsSub$ = this.programService.getActualPrograms().subscribe({ + next: ({ results: programs }) => { + const resultPrograms = programs.filter( + (program: Program) => Date.now() < Date.parse(program.datetimeRegistrationEnds) + ); + this.programs.set(resultPrograms.slice(0, 3)); + }, + }); + + this.subscriptions$.push(programsSub$); } ngOnDestroy(): void { diff --git a/projects/social_platform/src/app/office/program/detail/list/list-all.resolver.ts b/projects/social_platform/src/app/office/program/detail/list/list-all.resolver.ts index 5a7b30438..7b6db7737 100644 --- a/projects/social_platform/src/app/office/program/detail/list/list-all.resolver.ts +++ b/projects/social_platform/src/app/office/program/detail/list/list-all.resolver.ts @@ -1,5 +1,6 @@ /** @format */ +import { HttpParams } from "@angular/common/http"; import { inject } from "@angular/core"; import { ActivatedRouteSnapshot, ResolveFn, Router } from "@angular/router"; import { ApiPagination } from "@office/models/api-pagination.model"; @@ -48,17 +49,22 @@ export const ListAllResolver: ResolveFn> = ( const projectRatingService = inject(ProjectRatingService); const router = inject(Router); - return projectRatingService.getAll(route.parent?.params["programId"], 0, 8).pipe( - catchError(error => { - if (error.status === 403) { - router.navigate([], { - queryParams: { access: "accessDenied" }, - queryParamsHandling: "merge", - replaceUrl: true, - }); - } + return projectRatingService + .getAll( + route.parent?.params["programId"], + new HttpParams({ fromObject: { offset: 0, limit: 8 } }) + ) + .pipe( + catchError(error => { + if (error.status === 403) { + router.navigate([], { + queryParams: { access: "accessDenied" }, + queryParamsHandling: "merge", + replaceUrl: true, + }); + } - return EMPTY; - }) - ); + return EMPTY; + }) + ); }; diff --git a/projects/social_platform/src/app/office/program/models/program.model.ts b/projects/social_platform/src/app/office/program/models/program.model.ts index 8ace1b6f6..2ba3be164 100644 --- a/projects/social_platform/src/app/office/program/models/program.model.ts +++ b/projects/social_platform/src/app/office/program/models/program.model.ts @@ -50,11 +50,14 @@ export class Program { datetimeRegistrationEnds!: string; datetimeStarted!: string; datetimeFinished!: string; + datetimeProjectSubmissionEnds!: string; + datetimeEvaluationEnds!: string; viewsCount!: number; likesCount!: number; isUserLiked!: boolean; isUserManager!: boolean; isUserMember!: boolean; + publishProjectsAfterFinish!: boolean; static default(): Program { return { @@ -73,6 +76,8 @@ export class Program { datetimeRegistrationEnds: "", datetimeStarted: "", datetimeFinished: "", + datetimeProjectSubmissionEnds: "", + datetimeEvaluationEnds: "", viewsCount: 1, tag: "", likesCount: 1, @@ -80,6 +85,7 @@ export class Program { isUserLiked: false, isUserMember: false, isUserManager: false, + publishProjectsAfterFinish: false, }; } } diff --git a/projects/social_platform/src/app/office/program/services/program.service.ts b/projects/social_platform/src/app/office/program/services/program.service.ts index f5e79ed12..f9e261853 100644 --- a/projects/social_platform/src/app/office/program/services/program.service.ts +++ b/projects/social_platform/src/app/office/program/services/program.service.ts @@ -10,6 +10,7 @@ import { Project } from "@models/project.model"; import { ApiPagination } from "@models/api-pagination.model"; import { User } from "@auth/models/user.model"; import { PartnerProgramFields } from "@office/models/partner-program-fields.model"; +import { ProjectAdditionalFields } from "@office/projects/models/project-additional-fields.model"; /** * Сервис для работы с программами @@ -67,6 +68,10 @@ export class ProgramService { return this.apiService.get(`${this.PROGRAMS_URL}/`, httpParams); } + getActualPrograms(): Observable> { + return this.apiService.get(`${this.PROGRAMS_URL}/`); + } + getOne(programId: number): Observable { return this.apiService.get(`${this.PROGRAMS_URL}/${programId}/`); } @@ -103,6 +108,15 @@ export class ProgramService { return this.apiService.get(`${this.PROGRAMS_URL}/${programId}/filters/`); } + getProgramProjectAdditionalFields(programId: number): Observable { + return this.apiService.get(`${this.PROGRAMS_URL}/${programId}/projects/apply/`); + } + + // body - это форма проекта который подается + programFieldValues + applyProjectToProgram(programId: number, body: any): Observable { + return this.apiService.post(`${this.PROGRAMS_URL}/${programId}/projects/apply/`, body); + } + createProgramFilters( programId: number, filters: Record, diff --git a/projects/social_platform/src/app/office/program/shared/program-card/program-card.component.html b/projects/social_platform/src/app/office/program/shared/program-card/program-card.component.html index 7f8e20098..4c6a029f2 100644 --- a/projects/social_platform/src/app/office/program/shared/program-card/program-card.component.html +++ b/projects/social_platform/src/app/office/program/shared/program-card/program-card.component.html @@ -20,7 +20,7 @@ {{ !registerDateExpired && (!program.isUserMember || !program.isUserManager) - ? "Подача проектов до:" + " " + (program.datetimeRegistrationEnds | date: "dd MMMM") + ? "Регистрация до:" + " " + (program.datetimeRegistrationEnds | date: "dd MMMM") : registerDateExpired ? "регистрация завершена" : "ты уже участвуешь!" diff --git a/projects/social_platform/src/app/office/projects/edit/edit.component.ts b/projects/social_platform/src/app/office/projects/edit/edit.component.ts index c59b20aa5..af4f3bf91 100644 --- a/projects/social_platform/src/app/office/projects/edit/edit.component.ts +++ b/projects/social_platform/src/app/office/projects/edit/edit.component.ts @@ -47,15 +47,13 @@ import { ProjectTeamService } from "./services/project-team.service"; import { ProjectAdditionalStepComponent } from "./shared/project-additional-step/project-additional-step.component"; import { ProjectAdditionalService } from "./services/project-additional.service"; import { ProjectAchievementsService } from "./services/project-achievements.service"; -import { Goal, GoalPostForm } from "@office/models/goals.model"; +import { Goal } from "@office/models/goals.model"; import { ProjectGoalService } from "./services/project-goals.service"; import { SnackbarService } from "@ui/services/snackbar.service"; -import { Resource, ResourcePostForm } from "@office/models/resource.model"; +import { Resource } from "@office/models/resource.model"; import { Partner } from "@office/models/partner.model"; import { ProjectPartnerService } from "./services/project-partner.service"; import { ProjectResourceService } from "./services/project-resources.service"; -import { HttpErrorResponse } from "@angular/common/http"; -import { ProjectAssign } from "../models/project-assign.model"; /** * Компонент редактирования проекта @@ -120,6 +118,7 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { private readonly snackBarService: SnackbarService, private readonly skillsService: SkillsService, private readonly projectAdditionalService: ProjectAdditionalService, + private readonly programService: ProgramService, private readonly projectGoalService: ProjectGoalService ) {} @@ -175,9 +174,6 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { this.projectAdditionalService.clearAssignProjectToProgramError(); } - // Сигналы для работы с модальными окнами с текстом - assignProjectToProgramModalMessage = signal(null); - // Геттеры для работы с целями get goals(): FormArray { return this.projectGoalsService.goals; @@ -286,32 +282,32 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { this.projectStepService.navigateToStep(step); } - /** - * Привязка проекта к программе выбранной - * Перенаправление её на редактирование "нового" проекта - */ - assignProjectToProgram(): void { - this.projectService - .assignProjectToProgram( - Number(this.route.snapshot.paramMap.get("projectId")), - this.projectForm.get("partnerProgramId")?.value - ) - .subscribe({ - next: r => { - this.assignProjectToProgramModalMessage.set(r); - this.isAssignProjectToProgramModalOpen.set(true); - this.router.navigateByUrl(`/office/projects/${r.newProjectId}/edit?editingStep=main`); - }, - - error: err => { - if (err instanceof HttpErrorResponse) { - if (err.status === 400) { - this.setAssignProjectToProgramError(err.error); - } - } - }, - }); - } + // /** + // * Привязка проекта к программе выбранной + // * Перенаправление её на редактирование "нового" проекта + // */ + // assignProjectToProgram(): void { + // this.projectService + // .assignProjectToProgram( + // Number(this.route.snapshot.paramMap.get("projectId")), + // this.projectForm.get("partnerProgramId")?.value + // ) + // .subscribe({ + // next: r => { + // this.assignProjectToProgramModalMessage.set(r); + // this.isAssignProjectToProgramModalOpen.set(true); + // this.router.navigateByUrl(`/office/projects/${r.newProjectId}/edit?editingStep=main`); + // }, + + // error: err => { + // if (err instanceof HttpErrorResponse) { + // if (err.status === 400) { + // this.setAssignProjectToProgramError(err.error); + // } + // } + // }, + // }); + // } // Методы для управления состоянием отправки форм setIsSubmittingAsPublished(status: boolean): void { @@ -352,9 +348,15 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { return; } + const programId = this.projectForm.get("partnerProgramId")?.value; + this.projectService.remove(Number(this.route.snapshot.paramMap.get("projectId"))).subscribe({ next: () => { - this.router.navigateByUrl(`/office/projects/my`); + if (this.fromProgram) { + this.router.navigateByUrl(`/office/program/${programId}`); + } else { + this.router.navigateByUrl(`/office/projects/my`); + } }, }); } @@ -566,7 +568,6 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { */ private sendAdditionalFields(projectId: number, relationId: number): void { const isDraft = this.projectForm.get("draft")?.value === true; - this.projectAdditionalService.sendAdditionalFieldsValues(projectId).subscribe({ next: () => { if (!isDraft) { diff --git a/projects/social_platform/src/app/office/projects/edit/services/project-additional.service.ts b/projects/social_platform/src/app/office/projects/edit/services/project-additional.service.ts index 5a83bfb54..8b27ae100 100644 --- a/projects/social_platform/src/app/office/projects/edit/services/project-additional.service.ts +++ b/projects/social_platform/src/app/office/projects/edit/services/project-additional.service.ts @@ -242,7 +242,7 @@ export class ProjectAdditionalService { control.addValidators([Validators.maxLength(500)]); break; case "textarea": - control.addValidators([Validators.maxLength(300)]); + control.addValidators([Validators.maxLength(500)]); break; } } diff --git a/projects/social_platform/src/app/office/projects/edit/services/project-form.service.ts b/projects/social_platform/src/app/office/projects/edit/services/project-form.service.ts index 43183b3d1..ceaeae0ad 100644 --- a/projects/social_platform/src/app/office/projects/edit/services/project-form.service.ts +++ b/projects/social_platform/src/app/office/projects/edit/services/project-form.service.ts @@ -13,6 +13,7 @@ import { ActivatedRoute } from "@angular/router"; import { PartnerProgramFields } from "@office/models/partner-program-fields.model"; import { Project } from "@office/models/project.model"; import { ProjectService } from "@office/services/project.service"; +import { optionalUrlOrMentionValidator } from "@utils/optionalUrl.validator"; import { stripNullish } from "@utils/stripNull"; import { concatMap, filter } from "rxjs"; /** @@ -33,6 +34,29 @@ export class ProjectFormService { this.initializeForm(); } + formModel = (this.projectForm = this.fb.group({ + imageAddress: [""], + name: ["", [Validators.required, Validators.maxLength(256)]], + region: ["", [Validators.required, Validators.maxLength(256)]], + implementationDeadline: [null], + trl: [null], + links: this.fb.array([]), + link: ["", optionalUrlOrMentionValidator], + industryId: [undefined], + description: ["", [Validators.maxLength(800)]], + presentationAddress: [""], + coverImageAddress: [""], + actuality: ["", [Validators.maxLength(1000)]], + targetAudience: ["", [Validators.maxLength(500)]], + problem: ["", [Validators.maxLength(1000)]], + partnerProgramId: [null], + achievements: this.fb.array([]), + title: [""], + status: [""], + + draft: [null], + })); + /** * Создает и настраивает основную форму проекта с набором контролов и валидаторов. * Подписывается на изменения полей 'presentationAddress' и 'coverImageAddress' для автосохранения при очищении. @@ -40,19 +64,19 @@ export class ProjectFormService { private initializeForm(): void { this.projectForm = this.fb.group({ imageAddress: [""], - name: ["", [Validators.required]], - region: ["", [Validators.required]], + name: ["", [Validators.required, Validators.maxLength(256)]], + region: ["", [Validators.required, Validators.maxLength(256)]], implementationDeadline: [null], trl: [null], links: this.fb.array([]), - link: ["", Validators.pattern(/^(https?:\/\/)/)], - industryId: [undefined, [Validators.required]], - description: ["", [Validators.required, Validators.minLength(0), Validators.maxLength(800)]], + link: ["", optionalUrlOrMentionValidator], + industryId: [undefined], + description: ["", [Validators.maxLength(800)]], presentationAddress: [""], - coverImageAddress: ["", [Validators.required]], - actuality: ["", [Validators.maxLength(400)]], - targetAudience: ["", [Validators.required, Validators.maxLength(400)]], - problem: ["", [Validators.required, Validators.maxLength(400)]], + coverImageAddress: [""], + actuality: ["", [Validators.maxLength(1000)]], + targetAudience: ["", [Validators.maxLength(500)]], + problem: ["", [Validators.maxLength(1000)]], partnerProgramId: [null], achievements: this.fb.array([]), title: [""], @@ -130,7 +154,7 @@ export class ProjectFormService { } links.forEach(link => { - linksFormArray.push(this.fb.control(link, [Validators.required])); + linksFormArray.push(this.fb.control(link, optionalUrlOrMentionValidator)); }); } @@ -193,7 +217,13 @@ export class ProjectFormService { * @returns объект значений формы без nullish */ public getFormValue(): any { - return stripNullish(this.projectForm.value); + const value = stripNullish(this.projectForm.value); + + if (Array.isArray(value["links"])) { + value["links"] = value["links"].map((v: string) => v?.trim()).filter((v: string) => !!v); + } + + return value; } // Геттеры для быстрого доступа к контролам основной формы diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.html b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.html index 7a7b83302..293f58894 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.html +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.html @@ -415,7 +415,7 @@
@if (hasLinks) {