From 12cf11f32c840ca1035939b5fe6dbf735c8396ef Mon Sep 17 00:00:00 2001 From: Awakich Date: Thu, 15 Jan 2026 14:36:49 +0300 Subject: [PATCH 1/4] add modals for no-projects, success & success invite logic --- .../features/detail/detail.component.html | 235 +++++++++--------- .../features/detail/detail.component.scss | 16 +- .../features/detail/detail.component.ts | 135 +++++----- .../invite-manage-card.component.scss | 2 +- 4 files changed, 204 insertions(+), 184 deletions(-) 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 1d17051f..a4a4e292 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 @@ -195,103 +195,6 @@ } } - - - - > } @else { пригласить } @if (+profile.id !== +info().id) { @@ -607,6 +509,122 @@

подтвердить владение н } + + @if (!showNoProjectsModal && showSendInviteModal) { + +
+ @if (profileProjects().length) { +
    +
    +

    выберите проект и роль для приглашения.

    + +
    + + @for (project of profileProjects(); track project.id) { +
  • +
    + +

    + {{ project.name | truncate: 20 }} +

    +
    + +
  • + } @if (inviteForm.get("role"); as role) { +
    + + @if ((role | controlError: "required")) { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } + + + отправить приглашение + + +
+ } +
+
+ } + + +
+
+

вы не являетесь лидером ни в одном проекте

+
+ + + + перейти в проекты +
+
+ + +
+ + +

+ приглашение отправлено +

+ + перейти в проекты +
+
+ } @if (profile) { @if (profile.id === info().id) {
@@ -655,25 +673,6 @@

подтвердить владение н

- - diff --git a/projects/social_platform/src/app/office/features/detail/detail.component.scss b/projects/social_platform/src/app/office/features/detail/detail.component.scss index 19a1f339..46710f0f 100644 --- a/projects/social_platform/src/app/office/features/detail/detail.component.scss +++ b/projects/social_platform/src/app/office/features/detail/detail.component.scss @@ -161,23 +161,24 @@ $detail-bar-mb: 12px; display: flex; flex-direction: column; gap: 10px; - align-items: center; max-height: none; &--scrollable { - max-height: 180px; + max-height: 250px; overflow-y: auto; } } + &__invites { + max-height: 250px; + } + &__item { display: flex; flex-grow: 1; align-items: center; justify-content: space-between; - width: 300px; - margin-bottom: 12px; - margin-left: 40px; + margin-top: 8px; &--info { display: flex; @@ -243,6 +244,11 @@ $detail-bar-mb: 12px; z-index: 2; cursor: pointer; } + + &__success { + margin-top: 10px; + color: var(--green); + } } .support { 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 48788f03..455ee884 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 @@ -2,7 +2,7 @@ import { CommonModule, Location } from "@angular/common"; import { ChangeDetectorRef, Component, inject, OnDestroy, OnInit, signal } from "@angular/core"; -import { ButtonComponent } from "@ui/components"; +import { ButtonComponent, InputComponent } from "@ui/components"; import { IconComponent } from "@uilib"; import { ModalComponent } from "@ui/components/modal/modal.component"; import { ActivatedRoute, Router, RouterModule } from "@angular/router"; @@ -30,6 +30,11 @@ import { projectNewAdditionalProgramVields, } from "@office/models/partner-program-fields.model"; import { saveFile } from "@utils/helpers/export-file"; +import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms"; +import { TruncatePipe } from "projects/core/src/lib/pipes/truncate.pipe"; +import { ControlErrorPipe, ValidationService } from "@corelib"; +import { ErrorMessage } from "@error/models/error-message"; +import { InviteService } from "@office/services/invite.service"; @Component({ selector: "app-detail", @@ -38,17 +43,22 @@ import { saveFile } from "@utils/helpers/export-file"; imports: [ CommonModule, RouterModule, + ReactiveFormsModule, IconComponent, ButtonComponent, ModalComponent, AvatarComponent, TooltipComponent, ApproveSkillComponent, + InputComponent, + TruncatePipe, + ControlErrorPipe, ], standalone: true, }) export class DeatilComponent implements OnInit, OnDestroy { private readonly authService = inject(AuthService); + private readonly fb = inject(FormBuilder); private readonly route = inject(ActivatedRoute); private readonly projectService = inject(ProjectService); private readonly programDataService = inject(ProgramDataService); @@ -62,11 +72,14 @@ export class DeatilComponent implements OnInit, OnDestroy { public readonly chatService = inject(ChatService); private readonly cdRef = inject(ChangeDetectorRef); private readonly programService = inject(ProgramService); + private readonly inviteService = inject(InviteService); + private readonly validationService = inject(ValidationService); private readonly projectFormService = inject(ProjectFormService); // Основные данные(типы данных, данные) info = signal(undefined); profile?: User; + profileProjects = signal([]); listType: "project" | "program" | "profile" = "project"; // Переменная для подсказок @@ -94,8 +107,7 @@ export class DeatilComponent implements OnInit, OnDestroy { isProfileFill = false; // Переменные для работы с модалкой подачи проекта - // selectedProjectId: number | null = null; - // dubplicatedProjectId = 0; + selectedProjectId: number | null = null; memberProjects: Project[] = []; userType = signal(undefined); @@ -120,6 +132,9 @@ export class DeatilComponent implements OnInit, OnDestroy { // Переменные для работы с подтверждением навыков showApproveSkillModal = false; + showSendInviteModal = false; + showNoProjectsModal = false; + showSuccessInviteModal = false; readAllModal = false; // Сигналы для работы с модальными окнами с текстом @@ -131,6 +146,12 @@ export class DeatilComponent implements OnInit, OnDestroy { return this.projectFormService.formModel; } + readonly inviteForm = this.fb.group({ + role: ["", Validators.required], + }); + + protected readonly errorMessage = ErrorMessage; + ngOnInit(): void { const listTypeSub$ = this.route.data.subscribe(data => { this.listType = data["listType"]; @@ -207,19 +228,19 @@ 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 { + // selectProject(): void { // if (this.selectedProjectId === null) { // return; // } @@ -228,12 +249,7 @@ export class DeatilComponent implements OnInit, OnDestroy { // project => project.id === this.selectedProjectId // ); - // if (!selectedProject) { - // this.snackbarService.error("Проект не найден. Попробуйте выбрать другой проект."); - // return; - // } - - // this.assignProjectToProgram(selectedProject!); + // console.log(selectedProject); // } // get isProjectSelected(): boolean { @@ -271,47 +287,6 @@ 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` - // ); - // } - /** * Закрытие модального окна выхода из проекта */ @@ -398,6 +373,39 @@ export class DeatilComponent implements OnInit, OnDestroy { }); } + /** + * Открывает модалку для отправки приглашения пользователю + * Проверяет какие отрендерить проекты где profile.id === leader + */ + inviteUser(): void { + if (!this.profileProjects().length) { + this.showNoProjectsModal = true; + } else { + this.showSendInviteModal = true; + } + } + + sendInvite(): void { + const role = this.inviteForm.get("role")?.value; + const userId = this.route.snapshot.params["id"]; + + if ( + !this.validationService.getFormValidation(this.inviteForm) || + this.selectedProjectId === null + ) { + return; + } + + this.inviteService.sendForUser(userId, this.selectedProjectId, role!).subscribe({ + next: () => { + this.showSendInviteModal = false; + this.showSuccessInviteModal = true; + this.inviteForm.reset(); + this.selectedProjectId = null; + }, + }); + } + /** * Перенаправляет на страницу с информацией в завивисимости от listType */ @@ -417,6 +425,10 @@ export class DeatilComponent implements OnInit, OnDestroy { } } + routingToMyProjects(): void { + this.router.navigateByUrl(`/office/projects/my`); + } + /** * Проверка завершения программы перед регистрацией */ @@ -533,10 +545,13 @@ export class DeatilComponent implements OnInit, OnDestroy { const profileInfoSub$ = this.authService.profile.subscribe({ next: profile => { this.profile = profile; + this.profileProjects.set( + this.profile?.projects.filter(project => project.leader === this.profile?.id) + ); - if (this.info()) { + if (this.info() && this.listType === "project") { this.isInProject = this.info() - ?.collaborators.map((person: Collaborator) => person.userId) + ?.collaborators?.map((person: Collaborator) => person.userId) .includes(profile.id); } }, diff --git a/projects/ui/src/lib/components/layout/invite-manage-card/invite-manage-card.component.scss b/projects/ui/src/lib/components/layout/invite-manage-card/invite-manage-card.component.scss index dafecd73..f9edf7cb 100644 --- a/projects/ui/src/lib/components/layout/invite-manage-card/invite-manage-card.component.scss +++ b/projects/ui/src/lib/components/layout/invite-manage-card/invite-manage-card.component.scss @@ -1,7 +1,7 @@ .invite { display: flex; flex-direction: column; - gap: 30px; + gap: 10px; align-items: flex-start; width: 100%; padding: 24px; From 6a41318fc7e3d5a10a8bee5d4b9d9883f11d3bd7 Mon Sep 17 00:00:00 2001 From: Awakich Date: Fri, 16 Jan 2026 13:31:22 +0300 Subject: [PATCH 2/4] add logic of getting leader projects & modals for errors --- .../src/app/auth/services/auth.service.ts | 10 ++++ .../features/detail/detail.component.html | 49 ++++++++++++++++++- .../features/detail/detail.component.ts | 24 +++++++-- 3 files changed, 77 insertions(+), 6 deletions(-) diff --git a/projects/social_platform/src/app/auth/services/auth.service.ts b/projects/social_platform/src/app/auth/services/auth.service.ts index 9c544e02..e93a13b9 100644 --- a/projects/social_platform/src/app/auth/services/auth.service.ts +++ b/projects/social_platform/src/app/auth/services/auth.service.ts @@ -11,6 +11,8 @@ import { RegisterResponse, } from "../models/http.model"; import { User, UserRole } from "../models/user.model"; +import { Project } from "@office/models/project.model"; +import { ApiPagination } from "@office/models/api-pagination.model"; /** * Сервис аутентификации и управления пользователями @@ -123,6 +125,14 @@ export class AuthService { ); } + /** + * Получить проекты где пользователь leader + * @returns Observable проектов внутри профиля + */ + getLeaderProjects(): Observable> { + return this.apiService.get(`${this.AUTH_USERS_URL}/projects/leader/`); + } + /** * Получить роли, которые может изменить текущий пользователь * @returns Observable с массивом изменяемых ролей 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 a4a4e292..d5f138e3 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 @@ -538,7 +538,7 @@

выберите проект и роль

- {{ project.name | truncate: 20 }} + {{ project.name ?? "Проект без названия" | truncate: 20 }}

выберите проект и роль + +
+
+

+ У данного участника уже есть активное приглашение в проект +

+
+ + + + перейти в проекты +
+
+ + +
+
+

+ Пользователь не зарегистрирован в программе, поэтому нельзя отправить приглашение + в данный проект +

+
+ + + + перейти в проекты +
+
+ { this.showSendInviteModal = false; this.showSuccessInviteModal = true; + this.inviteForm.reset(); this.selectedProjectId = null; }, + error: err => { + if (err.error.user[0].includes("проект относится к программе")) { + this.showNoInProgramModal = true; + } else if (err.error.user[0].includes("активное приглашение")) { + this.showActiveInviteModal = true; + } + }, }); } @@ -537,7 +548,13 @@ export class DeatilComponent implements OnInit, OnDestroy { this.isInProfileInfo(); - this.subscriptions.push(profileDataSub$); + const profileLeaderProjectsSub$ = this.authService.getLeaderProjects().subscribe({ + next: (projects: ApiPagination) => { + this.profileProjects.set(projects.results); + }, + }); + + this.subscriptions.push(profileDataSub$, profileLeaderProjectsSub$); } } @@ -545,9 +562,6 @@ export class DeatilComponent implements OnInit, OnDestroy { const profileInfoSub$ = this.authService.profile.subscribe({ next: profile => { this.profile = profile; - this.profileProjects.set( - this.profile?.projects.filter(project => project.leader === this.profile?.id) - ); if (this.info() && this.listType === "project") { this.isInProject = this.info() From 1d2fade96f088413e864c9ae5a6da4ea39dc9cf5 Mon Sep 17 00:00:00 2001 From: Awakich Date: Mon, 19 Jan 2026 12:25:26 +0300 Subject: [PATCH 3/4] change from user to sender --- .../social_platform/src/app/office/models/invite.model.ts | 2 ++ .../invite-manage-card/invite-manage-card.component.html | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/projects/social_platform/src/app/office/models/invite.model.ts b/projects/social_platform/src/app/office/models/invite.model.ts index aaba627e..09b8501d 100644 --- a/projects/social_platform/src/app/office/models/invite.model.ts +++ b/projects/social_platform/src/app/office/models/invite.model.ts @@ -24,4 +24,6 @@ export class Invite { specialization?: string; user!: User; + + sender!: User; } diff --git a/projects/ui/src/lib/components/layout/invite-manage-card/invite-manage-card.component.html b/projects/ui/src/lib/components/layout/invite-manage-card/invite-manage-card.component.html index 71d8c348..ec54b368 100644 --- a/projects/ui/src/lib/components/layout/invite-manage-card/invite-manage-card.component.html +++ b/projects/ui/src/lib/components/layout/invite-manage-card/invite-manage-card.component.html @@ -4,12 +4,12 @@
- +
- {{ invite.user.firstName }} {{ invite.user.lastName }}{{ invite.sender.firstName }} {{ invite.sender.lastName }}
приглашает вас в проект
"{{ invite.project.name | truncate: 20 }}" Date: Mon, 19 Jan 2026 13:20:04 +0300 Subject: [PATCH 4/4] add sender description to invite model --- projects/social_platform/src/app/office/models/invite.model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/social_platform/src/app/office/models/invite.model.ts b/projects/social_platform/src/app/office/models/invite.model.ts index 09b8501d..ddcd998c 100644 --- a/projects/social_platform/src/app/office/models/invite.model.ts +++ b/projects/social_platform/src/app/office/models/invite.model.ts @@ -11,7 +11,7 @@ import { Project } from "./project.model"; * - Информацию о проекте и роли * - Статус принятия приглашения * - Мотивационное письмо и специализацию - * - Данные пользователя-отправителя + * - Данные пользователя-отправителя и получателя */ export class Invite { id!: number;