From b69dbc08596ac5de49a8c77b1ecb928e4710a059 Mon Sep 17 00:00:00 2001 From: Awakich Date: Wed, 3 Sep 2025 15:30:23 +0300 Subject: [PATCH 001/126] add new design of button & new error icon --- .../info-block/info-block.component.html | 9 +- .../info-block/info-block.component.scss | 8 - .../skill-chooser.component.html | 4 +- .../skill-chooser.component.scss | 7 - .../skills-block/skills-block.component.html | 8 +- .../skills-block/skills-block.component.scss | 7 - .../trajectory-block.component.html | 7 +- .../trajectory-block.component.scss | 8 - .../profile/students/students.component.html | 11 +- .../profile/students/students.component.scss | 26 --- .../src/app/skills/list/list.component.html | 12 +- .../src/app/skills/list/list.component.scss | 10 - .../skill-card/skill-card.component.scss | 1 + .../subscription/subscription.component.html | 25 ++- .../subscription/subscription.component.scss | 9 - .../app/task/complete/complete.component.html | 6 +- .../app/task/subtask/subtask.component.html | 12 +- .../app/task/subtask/subtask.component.scss | 2 +- .../detail/info/info.component.html | 1 + .../detail/info/info.component.scss | 8 - .../trajectory/trajectory.component.html | 13 +- .../trajectory/trajectory.component.scss | 21 --- projects/skills/src/styles/_colors.scss | 29 +-- .../src/app/auth/login/login.component.html | 7 +- .../src/app/auth/login/login.component.scss | 8 +- .../app/auth/register/register.component.html | 9 +- .../reset-password.component.html | 1 + .../set-password/set-password.component.html | 4 +- .../feed/filter/feed-filter.component.html | 6 +- .../src/app/office/models/project.model.ts | 12 +- .../src/app/office/office.component.ts | 6 +- .../stage-one/stage-one.component.html | 2 +- .../stage-three/stage-three.component.html | 2 +- .../stage-two/stage-two.component.html | 2 +- .../stage-zero/stage-zero.component.html | 15 +- .../detail/profile-detail.component.html | 2 +- .../office/profile/edit/edit.component.html | 26 ++- .../office/profile/edit/edit.component.scss | 11 -- .../detail/register/register.component.html | 2 +- .../program-head/program-head.component.html | 2 +- .../rating-card/rating-card.component.html | 4 +- .../projects/detail/info/info.component.html | 9 +- .../office/projects/edit/edit.component.html | 15 +- .../office/projects/edit/edit.component.scss | 26 +-- .../office/projects/edit/edit.component.ts | 4 - .../edit/services/project-form.service.ts | 24 +-- .../project-achievement-step.component.html | 2 +- .../project-contacts-step.component.html | 2 +- .../project-main-step.component.html | 28 +-- .../project-main-step.component.ts | 12 +- .../project-team-step.component.html | 2 + .../project-vacancy-step.component.html | 9 +- .../project-vacancy-step.component.scss | 7 - .../projects-filter.component.html | 9 - .../projects-filter.component.ts | 29 --- .../office/projects/projects.component.html | 6 +- .../app/office/services/project.service.ts | 21 --- .../shared/img-card/img-card.component.html | 2 +- .../invite-card/invite-card.component.html | 5 +- .../invite-card/invite-card.component.scss | 5 - .../shared/news-card/news-card.component.html | 8 +- .../shared/news-card/news-card.component.scss | 6 +- .../response-card.component.html | 17 +- .../response-card.component.scss | 11 +- .../vacancies/detail/info/info.component.html | 18 +- .../filter/vacancy-filter.component.html | 4 +- .../filter/vacancy-filter.component.scss | 19 -- .../office/vacancy/send/send.component.html | 11 +- .../components/button/button.component.html | 3 + .../components/button/button.component.scss | 41 +++- .../ui/components/button/button.component.ts | 3 + .../file-upload-item.component.html | 2 +- .../ui/components/input/input.component.html | 2 +- .../ui/components/loader/loader.component.ts | 2 +- .../src/assets/icons/svg/color-error.svg | 3 - .../src/assets/icons/svg/error.svg | 3 + .../assets/icons/symbol/svg/sprite.css.svg | 2 +- .../social_platform/src/styles/_colors.scss | 22 +-- .../social_platform/src/styles/_rounded.scss | 1 + .../src/styles/pages/_project-detail.scss | 1 - .../ui/src/lib/components/layout/index.ts | 1 - .../invite-manage-card.component.html | 15 +- .../invite-manage-card.component.scss | 9 +- .../subscription-plans.component.html | 44 ----- .../subscription-plans.component.scss | 177 ------------------ .../subscription-plans.component.spec.ts | 24 --- .../subscription-plans.component.ts | 64 ------- 87 files changed, 343 insertions(+), 752 deletions(-) delete mode 100644 projects/social_platform/src/assets/icons/svg/color-error.svg create mode 100644 projects/social_platform/src/assets/icons/svg/error.svg delete mode 100644 projects/ui/src/lib/components/layout/subscription-plans/subscription-plans.component.html delete mode 100644 projects/ui/src/lib/components/layout/subscription-plans/subscription-plans.component.scss delete mode 100644 projects/ui/src/lib/components/layout/subscription-plans/subscription-plans.component.spec.ts delete mode 100644 projects/ui/src/lib/components/layout/subscription-plans/subscription-plans.component.ts diff --git a/projects/skills/src/app/profile/shared/info-block/info-block.component.html b/projects/skills/src/app/profile/shared/info-block/info-block.component.html index b9ddf024a..86bddefb7 100644 --- a/projects/skills/src/app/profile/shared/info-block/info-block.component.html +++ b/projects/skills/src/app/profile/shared/info-block/info-block.component.html @@ -41,7 +41,9 @@

{{ userData.firstName }} {{ userData.lastName }}

{{ userData.points }} {{ userData.points | pluralize: ["балл", "балла", "баллов"] }} - Вернуться на procollab.ru + Вернуться на procollab.ru @@ -54,7 +56,10 @@

{{ userData.firstName }} {{ userData.lastName }}

Подписка оформлена

Погрузись в мир знаний прямо сейчас

- Начать wave diff --git a/projects/skills/src/app/profile/shared/info-block/info-block.component.scss b/projects/skills/src/app/profile/shared/info-block/info-block.component.scss index a72b471f5..393ae2cda 100644 --- a/projects/skills/src/app/profile/shared/info-block/info-block.component.scss +++ b/projects/skills/src/app/profile/shared/info-block/info-block.component.scss @@ -216,14 +216,6 @@ color: var(--grey-for-text); } - app-button { - width: 70%; - - @include responsive.apply-desktop { - width: 40%; - } - } - &__wave { position: absolute; bottom: -10%; diff --git a/projects/skills/src/app/profile/shared/skill-chooser/skill-chooser.component.html b/projects/skills/src/app/profile/shared/skill-chooser/skill-chooser.component.html index b71e57259..47c1393df 100644 --- a/projects/skills/src/app/profile/shared/skill-chooser/skill-chooser.component.html +++ b/projects/skills/src/app/profile/shared/skill-chooser/skill-chooser.component.html @@ -1,7 +1,7 @@ @if (open && !nonConfirmerModalOpen()) { - +

Выберите 5 навыков

@@ -80,6 +80,7 @@

У вас нет активной по
У вас нет активной по > Навыки

Тебе предстоит выбрать 5 навыков, развитию которых будет посвящен месяц твоей подписки. Не торопись с выбором. Отменить или поменять его будет нельзя!

- Выбрать навыки месяца wave @@ -82,6 +86,7 @@

У вас нет активной по
У вас нет активной по > - Начать + Начать
@@ -15,7 +15,10 @@

У вас нет активной траектории!

Выберите нужную вам траекторию!

- Ок

diff --git a/projects/skills/src/app/profile/shared/trajectory-block/trajectory-block.component.scss b/projects/skills/src/app/profile/shared/trajectory-block/trajectory-block.component.scss index fb123cd1b..10ec062aa 100644 --- a/projects/skills/src/app/profile/shared/trajectory-block/trajectory-block.component.scss +++ b/projects/skills/src/app/profile/shared/trajectory-block/trajectory-block.component.scss @@ -94,12 +94,4 @@ margin-bottom: 24px; color: var(--grey-for-text); } - - app-button { - width: 70%; - - @include responsive.apply-desktop { - width: 40%; - } - } } diff --git a/projects/skills/src/app/profile/students/students.component.html b/projects/skills/src/app/profile/students/students.component.html index 3a2689bfe..ec399b4dd 100644 --- a/projects/skills/src/app/profile/students/students.component.html +++ b/projects/skills/src/app/profile/students/students.component.html @@ -22,7 +22,7 @@ 'https://app.procollab.ru/office/chats/' + student.student.id + '_' + student.mentorId " > - Чат с участником @@ -92,7 +92,10 @@
- Сохранить
@@ -108,9 +111,9 @@ {{ expandedStudentId === student.student.id ? "Скрыть" : "Посмотреть" }} статистику

@if (expandedStudentId === student.student.id) { - + } @else { - + } diff --git a/projects/skills/src/app/profile/students/students.component.scss b/projects/skills/src/app/profile/students/students.component.scss index a0bb2f1f2..688de9ece 100644 --- a/projects/skills/src/app/profile/students/students.component.scss +++ b/projects/skills/src/app/profile/students/students.component.scss @@ -74,12 +74,6 @@ color: var(--dark-grey); } - app-button { - &::ng-deep .button--inline { - height: 25px; - } - } - &__stats { position: relative; top: auto; @@ -194,26 +188,6 @@ &__meeting { color: var(--dark-grey); } - - &__save { - width: 100%; - - app-button { - &::ng-deep .button--inline { - min-height: 45px; - } - } - - @include responsive.apply-desktop { - width: 13%; - - app-button { - &::ng-deep .button--inline { - min-height: 38px; - } - } - } - } } .icon { diff --git a/projects/skills/src/app/skills/list/list.component.html b/projects/skills/src/app/skills/list/list.component.html index 8799a4439..a47032003 100644 --- a/projects/skills/src/app/skills/list/list.component.html +++ b/projects/skills/src/app/skills/list/list.component.html @@ -12,7 +12,13 @@ class="search__input text-body-14" /> - Найти + Найти @if (skills()) {
@@ -40,16 +46,16 @@

Назад {{ isFromTrajectoryModal() ? "К траекториям" : "Купить" }}

Ты действительно хочешь отменить подписку?

- + Отменить stars @@ -32,7 +38,13 @@ style="color: var(--black)" >

Включим автопродление подписки?

- Включить + Включить wave
@@ -90,6 +102,8 @@

--> Купить @if(subscriptionData()?.lastSubscriptionType !== null) { - + Отменить подписку } diff --git a/projects/skills/src/app/subscription/subscription.component.scss b/projects/skills/src/app/subscription/subscription.component.scss index 42a97e7e8..0deb8a78c 100644 --- a/projects/skills/src/app/subscription/subscription.component.scss +++ b/projects/skills/src/app/subscription/subscription.component.scss @@ -95,10 +95,6 @@ flex-grow: 1; } - app-button { - margin-top: auto; - } - &--primary { background-color: var(--accent); @@ -203,13 +199,8 @@ app-button { z-index: 100; - width: 200%; @include typography.body-12; - - @include responsive.apply-desktop { - width: 40%; - } } &__stars { diff --git a/projects/skills/src/app/task/complete/complete.component.html b/projects/skills/src/app/task/complete/complete.component.html index ba185f596..d949a97ea 100644 --- a/projects/skills/src/app/task/complete/complete.component.html +++ b/projects/skills/src/app/task/complete/complete.component.html @@ -44,11 +44,13 @@

ваш результат

- В меню навыков @if (res.nextTaskId) { - Следующее задание + Следующее задание } } diff --git a/projects/skills/src/app/task/subtask/subtask.component.html b/projects/skills/src/app/task/subtask/subtask.component.html index 28d5b1487..e21b147a6 100644 --- a/projects/skills/src/app/task/subtask/subtask.component.html +++ b/projects/skills/src/app/task/subtask/subtask.component.html @@ -37,7 +37,7 @@ >

} } - Продолжить wave @@ -74,7 +74,7 @@ >

} } - Продолжить wave @@ -121,7 +121,7 @@ >

} } - Продолжить wave @@ -168,7 +168,7 @@ >

} } - Продолжить wave @@ -215,7 +215,7 @@ >

} } - Продолжить wave @@ -232,7 +232,7 @@
Всё верно! Так держать
- Продолжить + Продолжить } diff --git a/projects/skills/src/app/task/subtask/subtask.component.scss b/projects/skills/src/app/task/subtask/subtask.component.scss index 4cfd0d951..89c9bb739 100644 --- a/projects/skills/src/app/task/subtask/subtask.component.scss +++ b/projects/skills/src/app/task/subtask/subtask.component.scss @@ -31,7 +31,7 @@ display: flex; justify-content: center; padding: 15px 10px 0; - border-radius: 15px 15px 0 0; + border-radius: var(--rounded-xl); transition: all 0.2s; transform: translateY(0%); diff --git a/projects/skills/src/app/trajectories/track-career/detail/info/info.component.html b/projects/skills/src/app/trajectories/track-career/detail/info/info.component.html index ed85caf5e..954f53e35 100644 --- a/projects/skills/src/app/trajectories/track-career/detail/info/info.component.html +++ b/projects/skills/src/app/trajectories/track-career/detail/info/info.component.html @@ -57,6 +57,7 @@ " >
@if (type() === "my") { >Подробнее --> Доступно в подписке Подтверждение
Назад Поехали @@ -188,16 +191,16 @@

У вас нет активной по
Назад КупитьУ вас уже есть активн
Перейти diff --git a/projects/skills/src/app/trajectories/track-career/shared/trajectory/trajectory.component.scss b/projects/skills/src/app/trajectories/track-career/shared/trajectory/trajectory.component.scss index 18d9f4270..ae0306472 100644 --- a/projects/skills/src/app/trajectories/track-career/shared/trajectory/trajectory.component.scss +++ b/projects/skills/src/app/trajectories/track-career/shared/trajectory/trajectory.component.scss @@ -261,17 +261,6 @@ gap: 5px; align-items: center; justify-content: center; - width: 100%; - margin-top: 10px; - - app-button { - width: 100%; - border-radius: 15px; - } - - @include responsive.apply-desktop { - width: 40%; - } } &__inner { @@ -396,16 +385,6 @@ &__buttons-group { display: flex; gap: 15px; - width: 100%; - - app-button { - width: 100%; - border-radius: 15px; - } - - @include responsive.apply-desktop { - width: 55%; - } } &__confirm { diff --git a/projects/skills/src/styles/_colors.scss b/projects/skills/src/styles/_colors.scss index b33cc176b..ce92f9e6e 100644 --- a/projects/skills/src/styles/_colors.scss +++ b/projects/skills/src/styles/_colors.scss @@ -4,31 +4,32 @@ :root { // ACCENT - --gradient: linear-gradient(90deg, #242424 0%, #6c27ff 50%); - --gradient-mild: linear-gradient(90deg, #501d53 0.3%, #3b1f72 22%, #6326e6 100%); - --accent: #6c27ff; - --accent-dark: #{color.adjust(#6c27ff, $blackness: 20%)}; + --gradient: linear-gradient(90deg, #242424 0%, #8A63E6 50%); + --gradient-mild: linear-gradient(90deg, #501d53 0.3%, #3b1f72 22%, #8A63E6 100%); + --accent: #8A63E6; + --accent-dark: #{color.adjust(#8A63E6, $blackness: 20%)}; --accent-mild: #{color.adjust(#6c27ff, $alpha: -0.4)}; - --accent-light: #f7f3ff; + --accent-light: #9A80E6; // GOLD --gold: #f6ff8b; --gold-dark: #f7cf4d; // GRAY - --white: #fff; - --black: #332e2d; - --dark-grey: #8c888a; + --white: #FAFAFA; + --black: #333333; + --dark-grey: #E7E7E7; --gray: #d3d3d3; --light-gray: #f9f9f9; - --gray-for-shadow: rgb(159 159 159 / 15%); --grey-button: #e5e5e5e5; + --gray-for-shadow: rgb(159 159 159 / 15%); + --medium-grey-for-outline: #eee; --grey-for-text: #a59fb9; // FUNCTIONAL - --green: #73c66e; - --light-green: #e3f8e9; - --red: #ff5151; - --red-dark: #{color.adjust(#ff5151, $blackness: 10%)}; - --light-red: #ffd2d2; + --green: #88C9A1; + --light-green: #97ecb8; + --red: #D48A9E; + --red-dark: #{color.adjust(#D48A9E, $blackness: 10%)}; + --light-red: #e8a5b7; } 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 195d0a34f..e6e5052a7 100644 --- a/projects/social_platform/src/app/auth/login/login.component.html +++ b/projects/social_platform/src/app/auth/login/login.component.html @@ -26,7 +26,10 @@

Вход

} @if (loginForm.get("password"); as password) {
- + Вход

} Вход appViewBox="0 0 18 9" > -
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 e9212042e..7c15ef20f 100644 --- a/projects/social_platform/src/app/auth/login/login.component.scss +++ b/projects/social_platform/src/app/auth/login/login.component.scss @@ -16,7 +16,6 @@ &__forget { display: block; - margin-top: 12px; text-align: center; @include typography.body-12; @@ -27,6 +26,13 @@ @include typography.body-14; } } + + &__password { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 12px; + } } .icon { 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 90ec2710c..ea9767fc9 100644 --- a/projects/social_platform/src/app/auth/register/register.component.html +++ b/projects/social_platform/src/app/auth/register/register.component.html @@ -251,11 +251,16 @@

>Нажимая на кнопку подтверждаете, что вам больше 14 лет - + Далее } @else if (step === "info") { - + Создать аккаунт } diff --git a/projects/social_platform/src/app/auth/reset-password/reset-password.component.html b/projects/social_platform/src/app/auth/reset-password/reset-password.component.html index 54a2eaa70..07d86caa6 100644 --- a/projects/social_platform/src/app/auth/reset-password/reset-password.component.html +++ b/projects/social_platform/src/app/auth/reset-password/reset-password.component.html @@ -27,6 +27,7 @@

Забыли пароль?

} Новый пароль

{{ errorMessage.AUTH_WRONG_AUTH }} } - Готово + + Готово + diff --git a/projects/social_platform/src/app/office/feed/filter/feed-filter.component.html b/projects/social_platform/src/app/office/feed/filter/feed-filter.component.html index 37b428afa..a562f2ca0 100644 --- a/projects/social_platform/src/app/office/feed/filter/feed-filter.component.html +++ b/projects/social_platform/src/app/office/feed/filter/feed-filter.component.html @@ -7,7 +7,7 @@

Фильтр

- Применить
@@ -41,7 +41,9 @@

Фильтр

> -Написать новость +Написать новость
diff --git a/projects/social_platform/src/app/office/models/project.model.ts b/projects/social_platform/src/app/office/models/project.model.ts index 4da6aaa5f..b3b236c25 100644 --- a/projects/social_platform/src/app/office/models/project.model.ts +++ b/projects/social_platform/src/app/office/models/project.model.ts @@ -30,10 +30,10 @@ export class Project { id!: number; name!: string; description!: string; - track!: string; - direction!: string; + targetAudience!: string; + implementationDeadline!: string; + trl!: string; actuality!: string; - goal!: string; problem!: string; region!: string; step!: number; @@ -63,10 +63,10 @@ export class Project { name: "string", region: "sdf", step: 1, - track: "", - direction: "", + targetAudience: "", + implementationDeadline: "", actuality: "", - goal: "", + trl: "", problem: "", description: "string", shortDescription: "string", diff --git a/projects/social_platform/src/app/office/office.component.ts b/projects/social_platform/src/app/office/office.component.ts index ededf8a03..f1e7684d3 100644 --- a/projects/social_platform/src/app/office/office.component.ts +++ b/projects/social_platform/src/app/office/office.component.ts @@ -54,17 +54,13 @@ export class OfficeComponent implements OnInit, OnDestroy { private readonly industryService: IndustryService, private readonly route: ActivatedRoute, public readonly authService: AuthService, - private readonly projectService: ProjectService, private readonly inviteService: InviteService, private readonly router: Router, public readonly chatService: ChatService ) {} ngOnInit(): void { - const globalSubscription$ = forkJoin([ - this.industryService.getAll(), - this.projectService.getProjectSteps(), - ]).subscribe(noop); + const globalSubscription$ = forkJoin([this.industryService.getAll()]).subscribe(noop); this.subscriptions$.push(globalSubscription$); const profileSub$ = this.authService.profile.subscribe(profile => { diff --git a/projects/social_platform/src/app/office/onboarding/stage-one/stage-one.component.html b/projects/social_platform/src/app/office/onboarding/stage-one/stage-one.component.html index d6cf9e867..db9276e35 100644 --- a/projects/social_platform/src/app/office/onboarding/stage-one/stage-one.component.html +++ b/projects/social_platform/src/app/office/onboarding/stage-one/stage-one.component.html @@ -77,7 +77,7 @@

Библиотека

Закончить регистрацию позже - Продолжить
diff --git a/projects/social_platform/src/app/office/onboarding/stage-three/stage-three.component.html b/projects/social_platform/src/app/office/onboarding/stage-three/stage-three.component.html index f331d4d47..a2822fbf7 100644 --- a/projects/social_platform/src/app/office/onboarding/stage-three/stage-three.component.html +++ b/projects/social_platform/src/app/office/onboarding/stage-three/stage-three.component.html @@ -39,7 +39,7 @@

Выбери, кем ты будешь на платфо - Продолжить @if (stageTouched && userRole === -1) { diff --git a/projects/social_platform/src/app/office/onboarding/stage-two/stage-two.component.html b/projects/social_platform/src/app/office/onboarding/stage-two/stage-two.component.html index cd0c5e989..15fb15986 100644 --- a/projects/social_platform/src/app/office/onboarding/stage-two/stage-two.component.html +++ b/projects/social_platform/src/app/office/onboarding/stage-two/stage-two.component.html @@ -81,7 +81,7 @@

Библиотека

Закончить регистрацию позже - Продолжить diff --git a/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.html b/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.html index e0e8ab457..9242297b8 100644 --- a/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.html +++ b/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.html @@ -216,6 +216,7 @@

Привет, {{ profile.firstName }} {{ profile.lastNam
Привет, {{ profile.firstName }} {{ profile.lastNam
Привет, {{ profile.firstName }} {{ profile.lastNam } } - + Удалить @@ -501,7 +508,7 @@

Привет, {{ profile.firstName }} {{ profile.lastNam } - + Добавить достижение @@ -565,6 +572,7 @@

Привет, {{ profile.firstName }} {{ profile.lastNam

Привет, {{ profile.firstName }} {{ profile.lastNam
- Закончить регистрацию позже {{ user.firstName }} {{ user.lastName }}

class="info__send-message" [routerLink]="['/office/chats', profile.id + '_' + user.id]" > - Написать + Написать } @else {
diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.html b/projects/social_platform/src/app/office/profile/edit/edit.component.html index 01611cc13..e20dcb11d 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.html +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.html @@ -5,7 +5,9 @@

Редактировать профиль

- Назад + Назад
@@ -189,12 +191,8 @@ placeholder="Java Developer" formControlName="speciality" > - - + +
@if (speciality | controlError: "required") { @@ -400,6 +398,7 @@
+ Добавить достижение @@ -647,12 +647,8 @@ (searchStart)="onSearchSkill($event)" (optionSelected)="onAddSkill($event)" > - - + +
@@ -719,6 +715,7 @@
Зарегистрироваться в программе + Зарегистрироваться в программе }
diff --git a/projects/social_platform/src/app/office/program/shared/program-head/program-head.component.html b/projects/social_platform/src/app/office/program/shared/program-head/program-head.component.html index c52d425b2..146d6c386 100644 --- a/projects/social_platform/src/app/office/program/shared/program-head/program-head.component.html +++ b/projects/social_platform/src/app/office/program/shared/program-head/program-head.component.html @@ -16,7 +16,7 @@

{{ program.name }}

@if (isProjectsList && (isUserExpert | async)) { Оценить проектыОценить проекты } } diff --git a/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.html b/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.html index bf2e7f4a0..6dbdbd134 100644 --- a/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.html +++ b/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.html @@ -7,7 +7,7 @@

Фильтр

- Применить
@@ -36,7 +36,7 @@

Фильтр

} - Применить diff --git a/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.scss b/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.scss index 243849c89..028408232 100644 --- a/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.scss +++ b/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.scss @@ -26,18 +26,6 @@ justify-content: space-between; width: 100%; } - - &__actions { - align-self: flex-start; - width: 50%; - - app-button { - &::ng-deep .button--inline { - min-height: 38px; - padding: 0; - } - } - } } .mobile { @@ -48,13 +36,6 @@ @include responsive.apply-desktop { display: none; } - - app-button { - &::ng-deep .button--inline { - min-height: 38px; - padding: 0; - } - } } .select { diff --git a/projects/social_platform/src/app/office/vacancy/send/send.component.html b/projects/social_platform/src/app/office/vacancy/send/send.component.html index 2589d6bbf..a36b757a6 100644 --- a/projects/social_platform/src/app/office/vacancy/send/send.component.html +++ b/projects/social_platform/src/app/office/vacancy/send/send.component.html @@ -12,7 +12,9 @@ [routerLink]="['/office/profile', profile.id]" [skipLocationChange]="true" > - Вернуться в профиль + Вернуться в профиль
@@ -79,7 +81,9 @@

- Прикрепить резюме от PROCOLLAB + Прикрепить резюме от PROCOLLAB
Памятка перед отправкой откл
Отправить отклик - + diff --git a/projects/social_platform/src/app/ui/components/button/button.component.html b/projects/social_platform/src/app/ui/components/button/button.component.html index b75b28928..18145da6a 100644 --- a/projects/social_platform/src/app/ui/components/button/button.component.html +++ b/projects/social_platform/src/app/ui/components/button/button.component.html @@ -13,6 +13,9 @@ 'button--gold': color === 'gold', 'button--gradient': color === 'gradient', 'button--white': color === 'white', + 'button--small': size === 'small', + 'button--medium': size === 'medium', + 'button--big': size === 'big' }" [style.background-color]="backgroundColor" [disabled]="disabled" diff --git a/projects/social_platform/src/app/ui/components/button/button.component.scss b/projects/social_platform/src/app/ui/components/button/button.component.scss index 9e5d9956c..dbc52daad 100644 --- a/projects/social_platform/src/app/ui/components/button/button.component.scss +++ b/projects/social_platform/src/app/ui/components/button/button.component.scss @@ -5,21 +5,20 @@ display: flex; align-items: center; justify-content: center; - width: 100%; max-height: 52px; - padding: 11.5px 18px; font-weight: 400; color: var(--white); text-align: center; cursor: pointer; background: var(--accent); border: 2px solid transparent; - border-radius: var(--rounded-lg); + border-radius: var(--rounded-xl); outline: none; transition: background-color 0.2s; &:hover { - background-color: var(--accent-dark); + background-color: var(--accent-light); + box-shadow: -2px 3px 3px rgba(51, 51, 51, 0.2); } ::ng-deep *:not(.dot-wave) { @@ -66,6 +65,19 @@ background: var(--white); } + &.button--small { + padding: 12px 24px; + } + + &.button--medium { + padding: 12px 60px; + } + + &.button--big { + padding: 12px 24px; + width: 100%; + } + &.button--no-border { border: none; } @@ -75,20 +87,18 @@ display: flex; align-items: center; justify-content: center; - width: 100%; max-height: 52px; - padding: 11.5px 18px; color: var(--accent); text-align: center; cursor: pointer; background-color: transparent; border: 2px solid var(--accent); - border-radius: var(--rounded-lg); + border-radius: var(--rounded-xl); transition: all 0.2s; &:hover { - color: var(--accent-dark); - border-color: var(--accent-dark); + color: var(--accent-light); + border-color: var(--accent-light); } &.button--red { @@ -101,6 +111,19 @@ } } + &.button--small { + padding: 12px 24px; + } + + &.button--medium { + padding: 12px 36px; + } + + &.button--big { + padding: 12px 24px; + width: 100%; + } + &.button--no-border { border: none; } diff --git a/projects/social_platform/src/app/ui/components/button/button.component.ts b/projects/social_platform/src/app/ui/components/button/button.component.ts index bfb1f6ddf..6fca5b62c 100644 --- a/projects/social_platform/src/app/ui/components/button/button.component.ts +++ b/projects/social_platform/src/app/ui/components/button/button.component.ts @@ -38,6 +38,9 @@ export class ButtonComponent implements OnInit { /** Показывать индикатор загрузки */ @Input() loader = false; + /** Размер кнопки */ + @Input() size: "small" | "medium" | "big" = "small"; + /** Отображать рамку */ @Input() hasBorder = true; diff --git a/projects/social_platform/src/app/ui/components/file-upload-item/file-upload-item.component.html b/projects/social_platform/src/app/ui/components/file-upload-item/file-upload-item.component.html index 6f0cd53f9..da0b7aa12 100644 --- a/projects/social_platform/src/app/ui/components/file-upload-item/file-upload-item.component.html +++ b/projects/social_platform/src/app/ui/components/file-upload-item/file-upload-item.component.html @@ -17,7 +17,7 @@ } @else {
- + {{ error }}
} @if (loading && !error) { diff --git a/projects/social_platform/src/app/ui/components/input/input.component.html b/projects/social_platform/src/app/ui/components/input/input.component.html index de4e0f9ae..557f56dbb 100644 --- a/projects/social_platform/src/app/ui/components/input/input.component.html +++ b/projects/social_platform/src/app/ui/components/input/input.component.html @@ -17,7 +17,7 @@ [class.field__input--error]="error" /> @if (error) { - + }
diff --git a/projects/social_platform/src/app/ui/components/loader/loader.component.ts b/projects/social_platform/src/app/ui/components/loader/loader.component.ts index a99fecca0..28f92957b 100644 --- a/projects/social_platform/src/app/ui/components/loader/loader.component.ts +++ b/projects/social_platform/src/app/ui/components/loader/loader.component.ts @@ -35,7 +35,7 @@ export class LoaderComponent implements OnInit { @Input() color = "white"; /** Тип анимации */ - @Input() type: "wave" | "circle" = "wave"; + @Input() type: "wave" | "circle" = "circle"; ngOnInit(): void {} } diff --git a/projects/social_platform/src/assets/icons/svg/color-error.svg b/projects/social_platform/src/assets/icons/svg/color-error.svg deleted file mode 100644 index cbbf6afa7..000000000 --- a/projects/social_platform/src/assets/icons/svg/color-error.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/projects/social_platform/src/assets/icons/svg/error.svg b/projects/social_platform/src/assets/icons/svg/error.svg new file mode 100644 index 000000000..0c149fe7a --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/error.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg index daca6e2e3..21d60ce88 100644 --- a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg +++ b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/projects/social_platform/src/styles/_colors.scss b/projects/social_platform/src/styles/_colors.scss index 059cb1f09..bece0730e 100644 --- a/projects/social_platform/src/styles/_colors.scss +++ b/projects/social_platform/src/styles/_colors.scss @@ -4,21 +4,21 @@ :root { // ACCENT - --gradient: linear-gradient(90deg, #242424 0%, #6c27ff 50%); - --gradient-mild: linear-gradient(90deg, #501d53 0.3%, #3b1f72 22%, #6326e6 100%); - --accent: #6c27ff; - --accent-dark: #{color.adjust(#6c27ff, $blackness: 20%)}; + --gradient: linear-gradient(90deg, #242424 0%, #8A63E6 50%); + --gradient-mild: linear-gradient(90deg, #501d53 0.3%, #3b1f72 22%, #8A63E6 100%); + --accent: #8A63E6; + --accent-dark: #{color.adjust(#8A63E6, $blackness: 20%)}; --accent-mild: #{color.adjust(#6c27ff, $alpha: -0.4)}; - --accent-light: #f7f3ff; + --accent-light: #9A80E6; // GOLD --gold: #f6ff8b; --gold-dark: #f7cf4d; // GRAY - --white: #fff; - --black: #332e2d; - --dark-grey: #8c888a; + --white: #FAFAFA; + --black: #333333; + --dark-grey: #E7E7E7; --gray: #d3d3d3; --light-gray: #f9f9f9; --grey-button: #e5e5e5e5; @@ -26,7 +26,7 @@ --grey-for-text: #a59fb9; // FUNCTIONAL - --green: #73c66e; - --red: #ff5151; - --red-dark: #{color.adjust(#ff5151, $blackness: 10%)}; + --green: #88C9A1; + --red: #D48A9E; + --red-dark: #{color.adjust(#D48A9E, $blackness: 10%)}; } diff --git a/projects/social_platform/src/styles/_rounded.scss b/projects/social_platform/src/styles/_rounded.scss index 45c8a330d..91203f86a 100644 --- a/projects/social_platform/src/styles/_rounded.scss +++ b/projects/social_platform/src/styles/_rounded.scss @@ -4,4 +4,5 @@ --rounded-sm: 3px; --rounded-md: 5px; --rounded-lg: 8px; + --rounded-xl: 45px; } diff --git a/projects/social_platform/src/styles/pages/_project-detail.scss b/projects/social_platform/src/styles/pages/_project-detail.scss index be58925f5..49f776926 100644 --- a/projects/social_platform/src/styles/pages/_project-detail.scss +++ b/projects/social_platform/src/styles/pages/_project-detail.scss @@ -17,7 +17,6 @@ } app-button ::ng-deep .button--inline { - min-height: 38px; @include responsive.apply-desktop { min-height: 52px; diff --git a/projects/ui/src/lib/components/layout/index.ts b/projects/ui/src/lib/components/layout/index.ts index aa99379e9..a907933cb 100644 --- a/projects/ui/src/lib/components/layout/index.ts +++ b/projects/ui/src/lib/components/layout/index.ts @@ -14,4 +14,3 @@ export * from "./invite-manage-card/invite-manage-card.component"; export * from "./profile-control-panel/profile-control-panel.component"; export * from "./profile-info/profile-info.component"; export * from "./sidebar/sidebar.component"; -export * from "./subscription-plans/subscription-plans.component"; 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 562c1cebd..52539ceb0 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 @@ -11,8 +11,7 @@
{{ invite.user.firstName }} {{ invite.user.lastName }} - пригласил в проект + >, вас пригласили в проект "{{ invite.project.name }}"
@@ -22,8 +21,16 @@
- Отклонить - Принять + Отклонить + Принять
} 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 f04cef7a9..1fdd9703f 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 @@ -43,13 +43,6 @@ &__actions { display: flex; margin-top: 16px; - - app-button { - flex-basis: 50%; - - &:first-child { - margin-right: 15px; - } - } + gap: 12px; } } diff --git a/projects/ui/src/lib/components/layout/subscription-plans/subscription-plans.component.html b/projects/ui/src/lib/components/layout/subscription-plans/subscription-plans.component.html deleted file mode 100644 index b6d3b0022..000000000 --- a/projects/ui/src/lib/components/layout/subscription-plans/subscription-plans.component.html +++ /dev/null @@ -1,44 +0,0 @@ - - -@if (open) { - -} diff --git a/projects/ui/src/lib/components/layout/subscription-plans/subscription-plans.component.scss b/projects/ui/src/lib/components/layout/subscription-plans/subscription-plans.component.scss deleted file mode 100644 index d5449d01d..000000000 --- a/projects/ui/src/lib/components/layout/subscription-plans/subscription-plans.component.scss +++ /dev/null @@ -1,177 +0,0 @@ -@use "styles/responsive"; -@use "styles/typography"; - -.modal { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 50; - display: flex; - align-items: center; - justify-content: center; - - &__overlay { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background-color: var(--black); - opacity: 0.4; - } - - &__body { - position: relative; - min-width: 345px; - padding: 24px 16px; - background-color: var(--light-gray); - border-radius: 15px; - - @include responsive.apply-desktop { - min-width: 830px; - padding: 24px 24px 62px; - } - } -} - -.plans { - position: relative; - max-height: calc(100vh - 40px); - overflow-y: auto; - - &__title { - margin-bottom: 5px; - text-align: center; - - @include typography.heading-4; - - @include responsive.apply-desktop { - margin-bottom: 3px; - - @include typography.heading-3; - } - } - - &__subtitle { - color: var(--grey-for-text); - text-align: center; - - @include typography.body-14; - - @include responsive.apply-desktop { - @include typography.body-16; - } - } - - &__cross { - position: absolute; - top: 0; - right: 0; - width: 32px; - height: 32px; - cursor: pointer; - - @include responsive.apply-desktop { - top: 8px; - right: 8px; - } - } - - &__tariffs { - display: flex; - flex-direction: column; - gap: 20px; - justify-content: center; - margin-top: 20px; - - @include responsive.apply-desktop { - flex-direction: row; - margin-top: 16px; - } - } - - &__agreement { - display: flex; - align-items: center; - margin-top: 25px; - cursor: pointer; - - @include typography.body-12; - - @include responsive.apply-desktop { - margin-top: 20px; - - @include typography.body-14; - } - - app-checkbox { - margin-right: 15px; - } - } -} - -.tariff { - display: flex; - flex-direction: column; - justify-content: space-between; - width: 100%; - padding: 24px; - background-color: var(--white); - border: 1px solid var(--grey-button); - border-radius: 15px; - - &--primary { - background-color: var(--accent); - - .tariff__title { - color: var(--gold-dark); - } - - .tariff__price, - .tariff__point, - .tariff__icon { - color: var(--white); - } - } - - &__title { - @include typography.heading-3; - } - - &__price { - margin-bottom: 24px; - color: var(--grey-for-text); - - @include typography.heading-2; - } - - &__points { - display: flex; - flex-direction: column; - gap: 16px; - margin-bottom: 24px; - } - - &__point { - display: flex; - gap: 12px; - align-items: center; - color: var(--grey-for-text); - - @include typography.body-12; - } - - &__icon { - width: 24px; - height: 24px; - color: var(--accent); - } - - &__buy { - display: block; - width: 150px; - margin: 0 auto; - } -} diff --git a/projects/ui/src/lib/components/layout/subscription-plans/subscription-plans.component.spec.ts b/projects/ui/src/lib/components/layout/subscription-plans/subscription-plans.component.spec.ts deleted file mode 100644 index 2d68b9713..000000000 --- a/projects/ui/src/lib/components/layout/subscription-plans/subscription-plans.component.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** @format */ - -import { ComponentFixture, TestBed } from "@angular/core/testing"; - -import { SubscriptionPlansComponent } from "./subscription-plans.component"; - -describe("SubscriptionPlansComponent", () => { - let component: SubscriptionPlansComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [SubscriptionPlansComponent], - }).compileComponents(); - - fixture = TestBed.createComponent(SubscriptionPlansComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it("should create", () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/projects/ui/src/lib/components/layout/subscription-plans/subscription-plans.component.ts b/projects/ui/src/lib/components/layout/subscription-plans/subscription-plans.component.ts deleted file mode 100644 index 2c0521e3d..000000000 --- a/projects/ui/src/lib/components/layout/subscription-plans/subscription-plans.component.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** @format */ - -import { Component, EventEmitter, inject, Input, Output } from "@angular/core"; -import { CommonModule } from "@angular/common"; -import { IconComponent } from "@uilib"; -import { ButtonComponent, CheckboxComponent } from "@ui/components"; -import { Router, RouterLink } from "@angular/router"; -import { type SubscriptionPlan, SubscriptionPlansService } from "@corelib"; - -/** - * Компонент модального окна с планами подписки - * - * Отображает доступные тарифные планы с их особенностями и ценами. - * Позволяет пользователю выбрать и купить подписку. - * Включает чекбокс согласия с офертой. - * - * @example - * \`\`\`html - * - * - * \`\`\` - */ -@Component({ - selector: "app-subscription-plans", - standalone: true, - imports: [CommonModule, IconComponent, ButtonComponent, RouterLink, CheckboxComponent], - templateUrl: "./subscription-plans.component.html", - styleUrl: "./subscription-plans.component.scss", -}) -export class SubscriptionPlansComponent { - /** Сервис роутинга Angular */ - router = inject(Router); - - /** Сервис для работы с подписками */ - subscriptionService = inject(SubscriptionPlansService); - - /** Флаг согласия с офертой */ - offertAgreement = false; - - /** Флаг открытия модального окна */ - @Input() open = false; - - /** Массив доступных планов подписки */ - @Input({ required: true }) subscriptionPlans!: SubscriptionPlan[]; - - /** Событие изменения состояния модального окна */ - @Output() openChange = new EventEmitter(); - - /** - * Обработчик покупки подписки - * Инициирует процесс покупки выбранного плана - * - * @param planId - ID выбранного плана подписки - */ - onBuyClick(planId: SubscriptionPlan["id"]) { - this.subscriptionService.buySubscription(planId).subscribe(status => { - // Перенаправляем пользователя на страницу оплаты - location.href = status.confirmation.confirmationUrl; - }); - } -} From fd3a2c2eb37746676b042550e081045d8951d399 Mon Sep 17 00:00:00 2001 From: Awakich Date: Wed, 3 Sep 2025 15:31:04 +0300 Subject: [PATCH 002/126] fix styles depends on button --- .../skill-card/skill-card.component.scss | 2 +- projects/skills/src/styles/_colors.scss | 22 +++++++++---------- .../components/button/button.component.scss | 6 ++--- .../social_platform/src/styles/_colors.scss | 22 +++++++++---------- .../src/styles/pages/_project-detail.scss | 1 - .../invite-manage-card.component.scss | 2 +- 6 files changed, 27 insertions(+), 28 deletions(-) diff --git a/projects/skills/src/app/skills/shared/skill-card/skill-card.component.scss b/projects/skills/src/app/skills/shared/skill-card/skill-card.component.scss index fcdcbaed3..3cbb98f1f 100644 --- a/projects/skills/src/app/skills/shared/skill-card/skill-card.component.scss +++ b/projects/skills/src/app/skills/shared/skill-card/skill-card.component.scss @@ -4,9 +4,9 @@ display: flex; flex-direction: column; gap: 16px; + height: 220px; padding: 20px; margin-bottom: 20px; - height: 220px; cursor: pointer; border: 1px solid var(--grey-button); border-radius: 15px; diff --git a/projects/skills/src/styles/_colors.scss b/projects/skills/src/styles/_colors.scss index ce92f9e6e..4e52a6ba6 100644 --- a/projects/skills/src/styles/_colors.scss +++ b/projects/skills/src/styles/_colors.scss @@ -4,21 +4,21 @@ :root { // ACCENT - --gradient: linear-gradient(90deg, #242424 0%, #8A63E6 50%); - --gradient-mild: linear-gradient(90deg, #501d53 0.3%, #3b1f72 22%, #8A63E6 100%); - --accent: #8A63E6; - --accent-dark: #{color.adjust(#8A63E6, $blackness: 20%)}; + --gradient: linear-gradient(90deg, #242424 0%, #8a63e6 50%); + --gradient-mild: linear-gradient(90deg, #501d53 0.3%, #3b1f72 22%, #8a63e6 100%); + --accent: #8a63e6; + --accent-dark: #{color.adjust(#8a63e6, $blackness: 20%)}; --accent-mild: #{color.adjust(#6c27ff, $alpha: -0.4)}; - --accent-light: #9A80E6; + --accent-light: #9a80e6; // GOLD --gold: #f6ff8b; --gold-dark: #f7cf4d; // GRAY - --white: #FAFAFA; - --black: #333333; - --dark-grey: #E7E7E7; + --white: #fafafa; + --black: #333; + --dark-grey: #e7e7e7; --gray: #d3d3d3; --light-gray: #f9f9f9; --grey-button: #e5e5e5e5; @@ -27,9 +27,9 @@ --grey-for-text: #a59fb9; // FUNCTIONAL - --green: #88C9A1; + --green: #88c9a1; --light-green: #97ecb8; - --red: #D48A9E; - --red-dark: #{color.adjust(#D48A9E, $blackness: 10%)}; + --red: #d48a9e; + --red-dark: #{color.adjust(#d48a9e, $blackness: 10%)}; --light-red: #e8a5b7; } diff --git a/projects/social_platform/src/app/ui/components/button/button.component.scss b/projects/social_platform/src/app/ui/components/button/button.component.scss index dbc52daad..799343d21 100644 --- a/projects/social_platform/src/app/ui/components/button/button.component.scss +++ b/projects/social_platform/src/app/ui/components/button/button.component.scss @@ -18,7 +18,7 @@ &:hover { background-color: var(--accent-light); - box-shadow: -2px 3px 3px rgba(51, 51, 51, 0.2); + box-shadow: -2px 3px 3px rgb(51 51 51 / 20%); } ::ng-deep *:not(.dot-wave) { @@ -74,8 +74,8 @@ } &.button--big { - padding: 12px 24px; width: 100%; + padding: 12px 24px; } &.button--no-border { @@ -120,8 +120,8 @@ } &.button--big { - padding: 12px 24px; width: 100%; + padding: 12px 24px; } &.button--no-border { diff --git a/projects/social_platform/src/styles/_colors.scss b/projects/social_platform/src/styles/_colors.scss index bece0730e..5c2286c9a 100644 --- a/projects/social_platform/src/styles/_colors.scss +++ b/projects/social_platform/src/styles/_colors.scss @@ -4,21 +4,21 @@ :root { // ACCENT - --gradient: linear-gradient(90deg, #242424 0%, #8A63E6 50%); - --gradient-mild: linear-gradient(90deg, #501d53 0.3%, #3b1f72 22%, #8A63E6 100%); - --accent: #8A63E6; - --accent-dark: #{color.adjust(#8A63E6, $blackness: 20%)}; + --gradient: linear-gradient(90deg, #242424 0%, #8a63e6 50%); + --gradient-mild: linear-gradient(90deg, #501d53 0.3%, #3b1f72 22%, #8a63e6 100%); + --accent: #8a63e6; + --accent-dark: #{color.adjust(#8a63e6, $blackness: 20%)}; --accent-mild: #{color.adjust(#6c27ff, $alpha: -0.4)}; - --accent-light: #9A80E6; + --accent-light: #9a80e6; // GOLD --gold: #f6ff8b; --gold-dark: #f7cf4d; // GRAY - --white: #FAFAFA; - --black: #333333; - --dark-grey: #E7E7E7; + --white: #fafafa; + --black: #333; + --dark-grey: #e7e7e7; --gray: #d3d3d3; --light-gray: #f9f9f9; --grey-button: #e5e5e5e5; @@ -26,7 +26,7 @@ --grey-for-text: #a59fb9; // FUNCTIONAL - --green: #88C9A1; - --red: #D48A9E; - --red-dark: #{color.adjust(#D48A9E, $blackness: 10%)}; + --green: #88c9a1; + --red: #d48a9e; + --red-dark: #{color.adjust(#d48a9e, $blackness: 10%)}; } diff --git a/projects/social_platform/src/styles/pages/_project-detail.scss b/projects/social_platform/src/styles/pages/_project-detail.scss index 49f776926..8e9881f71 100644 --- a/projects/social_platform/src/styles/pages/_project-detail.scss +++ b/projects/social_platform/src/styles/pages/_project-detail.scss @@ -17,7 +17,6 @@ } app-button ::ng-deep .button--inline { - @include responsive.apply-desktop { min-height: 52px; } 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 1fdd9703f..0ab329215 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 @@ -42,7 +42,7 @@ &__actions { display: flex; - margin-top: 16px; gap: 12px; + margin-top: 16px; } } From 24c979a4013eecd7d90599f9cabc27e47b8014cb Mon Sep 17 00:00:00 2001 From: Awakich Date: Tue, 2 Sep 2025 11:57:25 +0300 Subject: [PATCH 003/126] fix angular to css budget --- angular.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/angular.json b/angular.json index 520a0658a..3593b57be 100644 --- a/angular.json +++ b/angular.json @@ -54,7 +54,7 @@ { "type": "anyComponentStyle", "maximumWarning": "2kb", - "maximumError": "10kb" + "maximumError": "15kb" } ], "outputHashing": "all" From f0d1d46a8a11d6e84cb6aa1022cf910530c2ab81 Mon Sep 17 00:00:00 2001 From: Awakich Date: Mon, 18 Aug 2025 17:10:05 +0300 Subject: [PATCH 004/126] fix register, stage-zero onboarding, shared modules --- projects/core/src/consts/list-years.ts | 2 +- .../src/lib/services/validation.service.ts | 46 +++++++++-- .../sidebar-profile.component.html | 4 +- .../app/auth/register/register.component.html | 12 ++- .../app/auth/register/register.component.ts | 5 +- .../src/app/error/models/error-message.ts | 1 + .../stage-two/stage-two.component.scss | 11 +++ .../stage-zero/stage-zero.component.html | 81 ++++++++++--------- .../stage-zero/stage-zero.component.scss | 16 +++- .../stage-zero/stage-zero.component.ts | 80 +++++++++++------- .../office/profile/edit/edit.component.html | 4 +- .../shared/news-card/news-card.component.html | 10 +-- .../projects/detail/info/info.component.html | 2 +- .../shared/news-card/news-card.component.html | 10 +-- .../ui/components/input/input.component.html | 7 +- .../ui/components/input/input.component.scss | 3 +- .../ui/components/input/input.component.ts | 7 +- .../components/search/search.component.scss | 2 +- .../components/select/select.component.scss | 2 +- .../profile-control-panel.component.scss | 1 + .../profile-info/profile-info.component.html | 4 +- 21 files changed, 208 insertions(+), 102 deletions(-) diff --git a/projects/core/src/consts/list-years.ts b/projects/core/src/consts/list-years.ts index 6024d80ed..d0923b012 100644 --- a/projects/core/src/consts/list-years.ts +++ b/projects/core/src/consts/list-years.ts @@ -164,6 +164,6 @@ export const yearList = [ { value: 2025, id: 25, - label: "настоящее время", + label: "по наст. вр.", }, ]; diff --git a/projects/core/src/lib/services/validation.service.ts b/projects/core/src/lib/services/validation.service.ts index bd358a0dc..bc42943d9 100644 --- a/projects/core/src/lib/services/validation.service.ts +++ b/projects/core/src/lib/services/validation.service.ts @@ -140,13 +140,49 @@ export class ValidationService { useAgeValidator(age = 14): ValidatorFn { return control => { const value = dayjs(control.value, "DD.MM.YYYY", true); + const difference = dayjs().diff(value, "year"); + + const isInvalidDate = !value.isValid() || value.year() < 1900; + const isTooYoung = difference < age; + const isTooOld = difference > 100; + + return isInvalidDate + ? { invalidDateFormat: { requiredAge: 100 } } + : isTooYoung + ? { tooYoung: { requiredAge: age } } + : isTooOld + ? { tooOld: { requiredAge: 100 } } + : null; + }; + } - if (value.isValid()) { - const difference = dayjs().diff(value, "year"); - return difference >= age ? null : { tooYoung: { requiredAge: age } }; + /** + * Создает валидатор для проверки валидности полного email + * @returns ValidatorFn для проверки возраста + * + * Применение: + * - Проверка валидности + * - Валидация полного email + * + * Логика: + * 1. Создаем регулрку для email + * 2. Тестируем подходит ли она нам + * + * Пример использования: + * email: ['', [ + * Validators.required, + * this.validationService.useEmailValidator() + * ]] + */ + useEmailValidator(): ValidatorFn { + return control => { + const value = control.value; + const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (regex.test(value)) { + return null; + } else { + return { invalidEmail: {} }; } - - return null; }; } diff --git a/projects/skills/src/app/shared/sidebar-profile/sidebar-profile.component.html b/projects/skills/src/app/shared/sidebar-profile/sidebar-profile.component.html index 37c8266a6..37c6c2922 100644 --- a/projects/skills/src/app/shared/sidebar-profile/sidebar-profile.component.html +++ b/projects/skills/src/app/shared/sidebar-profile/sidebar-profile.component.html @@ -20,13 +20,13 @@
{{ user()?.firstName }} {{ user()?.lastName }}
+ } @if (birthday | controlError: "tooOld") { +
+ @if (birthday.errors) { + {{ errorMessage.MAXIMAL_AGE }} + {{ birthday.errors["tooOld"]["requiredAge"] }} лет } +
} @if (birthday | controlError: "invalidDateFormat") {
{{ errorMessage.INVALID_DATE }} @@ -255,7 +261,11 @@

size="big" type="submit" class="auth__button" - customTypographyClass="auth__button-typography" + [disabled]="!(ageAgreement && registerAgreement)" + [ngStyle]="{ + opacity: !(ageAgreement && registerAgreement) ? '0.6' : '1', + cursor: !(ageAgreement && registerAgreement) ? 'not-allowed' : 'pointer' + }" > Далее diff --git a/projects/social_platform/src/app/auth/register/register.component.ts b/projects/social_platform/src/app/auth/register/register.component.ts index afc94dd4d..7e426b1b6 100644 --- a/projects/social_platform/src/app/auth/register/register.component.ts +++ b/projects/social_platform/src/app/auth/register/register.component.ts @@ -69,7 +69,10 @@ export class RegisterComponent implements OnInit { this.validationService.useAgeValidator(), ], ], - email: ["", [Validators.required, Validators.email]], + email: [ + "", + [Validators.required, Validators.email, this.validationService.useEmailValidator()], + ], password: ["", [Validators.required, Validators.minLength(6)]], repeatedPassword: ["", [Validators.required]], }, diff --git a/projects/social_platform/src/app/error/models/error-message.ts b/projects/social_platform/src/app/error/models/error-message.ts index 22d44b8f0..b937ad447 100644 --- a/projects/social_platform/src/app/error/models/error-message.ts +++ b/projects/social_platform/src/app/error/models/error-message.ts @@ -30,6 +30,7 @@ export enum ErrorMessage { VALIDATION_TOO_SHORT = "Минимальная длина:", VALIDATION_REQUIRED = "Обязательное поле", MINIMAL_AGE = "Минимальный возраст", + MAXIMAL_AGE = "Максимальный возраст", INVALID_DATE = "Неправильный формат даты", VALIDATION_LANGUAGE = "Используйте символы кириллического алфавита", VALIDATION_EMAIL = "Введенное значение не соответствует формату email", diff --git a/projects/social_platform/src/app/office/onboarding/stage-two/stage-two.component.scss b/projects/social_platform/src/app/office/onboarding/stage-two/stage-two.component.scss index 9df1c4526..93adf18a5 100644 --- a/projects/social_platform/src/app/office/onboarding/stage-two/stage-two.component.scss +++ b/projects/social_platform/src/app/office/onboarding/stage-two/stage-two.component.scss @@ -1,4 +1,5 @@ @use "styles/responsive"; +@use "styles/typography"; .auth { &__greeting { @@ -80,6 +81,16 @@ &__skills { display: flex; flex-direction: column; + + ::ng-deep { + app-autocomplete-input { + .field__input { + padding: 12px 20px; + + @include typography.body-16; + } + } + } } &__left { diff --git a/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.html b/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.html index 9242297b8..09c84559d 100644 --- a/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.html +++ b/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.html @@ -135,7 +135,7 @@

Привет, {{ profile.firstName }} {{ profile.lastNam @@ -166,7 +166,7 @@

Привет, {{ profile.firstName }} {{ profile.lastNam

} @if (stageForm.get("educationStatus"); as educationStatus) {
- + Привет, {{ profile.firstName }} {{ profile.lastNam class="edit" appIcon icon="edit-pen" - appSquare="20" + appSquare="24" (click)="editEducation($index)" > @@ -532,43 +532,48 @@

Привет, {{ profile.firstName }} {{ profile.lastNam >{{ tooltipLanguageText }} -
- @if (stageForm.get("language"); as language) { -
- - - - +
+
+ @if (stageForm.get("language"); as language) { +
+ + + + - @if (language | controlError: "required") { -
- {{ errorMessage.VALIDATION_REQUIRED }} -
- } -
- } @if (stageForm.get("languageLevel"); as languageLevel) { -
- - - + @if (language | controlError: "required") { +
+ {{ errorMessage.VALIDATION_REQUIRED }} +
+ } +
+ } @if (stageForm.get("languageLevel"); as languageLevel) { +
+ + + - @if (languageLevel | controlError: "required") { -
- {{ errorMessage.VALIDATION_REQUIRED }} -
+ @if (languageLevel | controlError: "required") { +
+ {{ errorMessage.VALIDATION_REQUIRED }} +
+ } +
} -
- } +
+ Количество добавляемых языков не более 4-х Привет, {{ profile.firstName }} {{ profile.lastNam
-

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

+

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

{{ isModalErrorYearText() }}.

diff --git a/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.scss b/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.scss index ced455749..a6470607e 100644 --- a/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.scss +++ b/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.scss @@ -47,11 +47,17 @@ display: flex; gap: 20px; align-items: center; + margin-bottom: 10px; + margin-top: 10px; .years__left, .years__right { width: 50%; } + + &--attention { + color: var(--dark-grey); + } } } @@ -82,7 +88,7 @@ position: absolute; top: 65%; left: 38%; - z-index: 100; + z-index: 10; display: none; width: 250px; padding: 12px; @@ -125,15 +131,16 @@ gap: 20px; align-items: center; justify-content: space-between; - width: 90%; padding: 12px; overflow: hidden; border: 1px solid var(--medium-grey-for-outline); border-radius: 15px; + width: 100%; } &__text { color: var(--dark-grey); + width: 90%; } &__remove { @@ -163,7 +170,6 @@ } .edit { - width: 10%; color: var(--dark-grey); cursor: pointer; } @@ -172,6 +178,10 @@ color: var(--red); } +i { + width: 24px; +} + .cancel { display: flex; flex-direction: column; diff --git a/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.ts b/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.ts index d88fff928..ca0977d10 100644 --- a/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.ts +++ b/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.ts @@ -652,42 +652,60 @@ export class OnboardingStageZeroComponent implements OnInit, OnDestroy { } addLanguage() { - ["language", "languageLevel"].forEach(name => this.stageForm.get(name)?.clearValidators()); - ["language", "languageLevel"].forEach(name => - this.stageForm.get(name)?.setValidators([Validators.required]) - ); - ["language", "languageLevel"].forEach(name => - this.stageForm.get(name)?.updateValueAndValidity() - ); - ["language", "languageLevel"].forEach(name => this.stageForm.get(name)?.markAsTouched()); + const languageValue = this.stageForm.get("language")?.value; + const languageLevelValue = this.stageForm.get("languageLevel")?.value; - const languageItem = this.fb.group({ - language: this.stageForm.get("language")?.value, - languageLevel: this.stageForm.get("languageLevel")?.value, + ["language", "languageLevel"].forEach(name => { + this.stageForm.get(name)?.clearValidators(); }); - if (this.editIndex() !== null) { - this.languageItems.update(items => { - const updatedItems = [...items]; - updatedItems[this.editIndex()!] = languageItem.value; - - this.userLanguages.at(this.editIndex()!).patchValue(languageItem.value); - return updatedItems; + if ((languageValue && !languageLevelValue) || (!languageValue && languageLevelValue)) { + ["language", "languageLevel"].forEach(name => { + this.stageForm.get(name)?.setValidators([Validators.required]); }); - this.editIndex.set(null); - } else { - this.languageItems.update(items => [...items, languageItem.value]); - this.userLanguages.push(languageItem); } + ["language", "languageLevel"].forEach(name => { - this.stageForm.get(name)?.reset(); - this.stageForm.get(name)?.setValue(""); - this.stageForm.get(name)?.clearValidators(); - this.stageForm.get(name)?.markAsPristine(); this.stageForm.get(name)?.updateValueAndValidity(); + this.stageForm.get(name)?.markAsTouched(); + }); + + const isLanguageValid = this.stageForm.get("language")?.valid; + const isLanguageLevelValid = this.stageForm.get("languageLevel")?.valid; + + if (!isLanguageValid || !isLanguageLevelValid) { + return; + } + + const languageItem = this.fb.group({ + language: languageValue, + languageLevel: languageLevelValue, }); - this.editLanguageClick = false; + if (languageValue && languageLevelValue) { + if (this.editIndex() !== null) { + this.languageItems.update(items => { + const updatedItems = [...items]; + updatedItems[this.editIndex()!] = languageItem.value; + + this.userLanguages.at(this.editIndex()!).patchValue(languageItem.value); + return updatedItems; + }); + this.editIndex.set(null); + } else { + this.languageItems.update(items => [...items, languageItem.value]); + this.userLanguages.push(languageItem); + } + ["language", "languageLevel"].forEach(name => { + this.stageForm.get(name)?.reset(); + this.stageForm.get(name)?.setValue(null); + this.stageForm.get(name)?.clearValidators(); + this.stageForm.get(name)?.markAsPristine(); + this.stageForm.get(name)?.updateValueAndValidity(); + }); + + this.editLanguageClick = false; + } } editLanguage(index: number) { @@ -763,7 +781,13 @@ export class OnboardingStageZeroComponent implements OnInit, OnDestroy { .pipe(concatMap(() => this.authService.setOnboardingStage(1))) .subscribe({ next: () => this.completeRegistration(1), - error: () => this.stageSubmitting.set(false), + error: error => { + this.stageSubmitting.set(false); + this.isModalErrorYear.set(true); + if (error.error.language) { + this.isModalErrorYearText.set(error.error.language); + } + }, }); } diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.html b/projects/social_platform/src/app/office/profile/edit/edit.component.html index e20dcb11d..e89935d3d 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.html +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.html @@ -285,7 +285,7 @@ [selectedId]="selectedComplitionYearEducationId()" formControlName="completionYear" placeholder="2023 год" - [options]="yearListEducation.slice(5)" + [options]="yearListEducation" > @@ -345,7 +345,7 @@

} @if (profileForm.get("educationStatus"); as educationStatus) {
- + {{ newsItem.likesCount }} - diff --git a/projects/social_platform/src/app/office/projects/detail/info/info.component.html b/projects/social_platform/src/app/office/projects/detail/info/info.component.html index 70f9b870b..4b5c27b31 100644 --- a/projects/social_platform/src/app/office/projects/detail/info/info.component.html +++ b/projects/social_platform/src/app/office/projects/detail/info/info.component.html @@ -286,7 +286,7 @@

Вакансии

} @if(profileId === project.leader){
Добавить вакансию diff --git a/projects/social_platform/src/app/office/shared/news-card/news-card.component.html b/projects/social_platform/src/app/office/shared/news-card/news-card.component.html index 742c7c9ef..54f4d2802 100644 --- a/projects/social_platform/src/app/office/shared/news-card/news-card.component.html +++ b/projects/social_platform/src/app/office/shared/news-card/news-card.component.html @@ -98,14 +98,14 @@ {{ feedItem.likesCount }} - diff --git a/projects/social_platform/src/app/ui/components/input/input.component.html b/projects/social_platform/src/app/ui/components/input/input.component.html index 557f56dbb..4f5b20fd9 100644 --- a/projects/social_platform/src/app/ui/components/input/input.component.html +++ b/projects/social_platform/src/app/ui/components/input/input.component.html @@ -19,7 +19,12 @@ @if (error) { } -
+
diff --git a/projects/social_platform/src/app/ui/components/input/input.component.scss b/projects/social_platform/src/app/ui/components/input/input.component.scss index 08215c8bb..f44fa86b3 100644 --- a/projects/social_platform/src/app/ui/components/input/input.component.scss +++ b/projects/social_platform/src/app/ui/components/input/input.component.scss @@ -48,8 +48,7 @@ &__right-icon { position: absolute; - top: 20%; - right: 24px; + top: 27%; display: flex; align-items: center; justify-content: center; diff --git a/projects/social_platform/src/app/ui/components/input/input.component.ts b/projects/social_platform/src/app/ui/components/input/input.component.ts index f42b3f330..80734f233 100644 --- a/projects/social_platform/src/app/ui/components/input/input.component.ts +++ b/projects/social_platform/src/app/ui/components/input/input.component.ts @@ -1,7 +1,8 @@ /** @format */ -import { Component, EventEmitter, forwardRef, Input, type OnInit, Output } from "@angular/core"; -import { type ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; +import { CommonModule } from "@angular/common"; +import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from "@angular/core"; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; import { IconComponent } from "@ui/components"; import { NgxMaskModule } from "ngx-mask"; @@ -35,7 +36,7 @@ import { NgxMaskModule } from "ngx-mask"; }, ], standalone: true, - imports: [NgxMaskModule, IconComponent], + imports: [CommonModule, NgxMaskModule, IconComponent], }) export class InputComponent implements OnInit, ControlValueAccessor { constructor() {} diff --git a/projects/social_platform/src/app/ui/components/search/search.component.scss b/projects/social_platform/src/app/ui/components/search/search.component.scss index 5aea863ae..bc4f8d116 100644 --- a/projects/social_platform/src/app/ui/components/search/search.component.scss +++ b/projects/social_platform/src/app/ui/components/search/search.component.scss @@ -16,7 +16,7 @@ } &__hidden { - height: 22px; + height: 18px; } &__input { diff --git a/projects/social_platform/src/app/ui/components/select/select.component.scss b/projects/social_platform/src/app/ui/components/select/select.component.scss index c6819a050..2a0471e56 100644 --- a/projects/social_platform/src/app/ui/components/select/select.component.scss +++ b/projects/social_platform/src/app/ui/components/select/select.component.scss @@ -43,7 +43,7 @@ right: 0; bottom: -6px; left: 0; - z-index: 3; + z-index: 11; max-height: 200px; padding: 10px; overflow-y: auto; diff --git a/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.scss b/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.scss index 0b3c901db..c6bf45676 100644 --- a/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.scss +++ b/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.scss @@ -60,6 +60,7 @@ padding: 26px 10px 10px; background-color: var(--light-gray); border-radius: var(--rounded-lg); + width: 100%; } } diff --git a/projects/ui/src/lib/components/layout/profile-info/profile-info.component.html b/projects/ui/src/lib/components/layout/profile-info/profile-info.component.html index 7b09eb0c1..509f3be5f 100644 --- a/projects/ui/src/lib/components/layout/profile-info/profile-info.component.html +++ b/projects/ui/src/lib/components/layout/profile-info/profile-info.component.html @@ -7,13 +7,13 @@
{{ user.firstName }} {{ user.lastName }}
- @if (user.verificationDate; as verificationDate) { +
    @for (p of user.programs.slice(0, 3); track p.id) { -
  • +
  • } diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.html b/projects/social_platform/src/app/office/profile/edit/edit.component.html index e89935d3d..e68059ec8 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.html +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.html @@ -4,10 +4,8 @@

    Редактировать профиль

    - - Назад + + Назад
    diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.ts b/projects/social_platform/src/app/office/profile/edit/edit.component.ts index 47b68ba01..70d804796 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.ts +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.ts @@ -39,14 +39,13 @@ import { ModalComponent } from "@ui/components/modal/modal.component"; import { Skill } from "@office/models/skill"; import { SkillsService } from "@office/services/skills.service"; import { navItems } from "projects/core/src/consts/navProfileItems"; -import { yearList } from "projects/core/src/consts/list-years"; import { educationUserLevel, educationUserType } from "projects/core/src/consts/list-education"; import { languageLevelsList, languageNamesList } from "projects/core/src/consts/list-language"; import { transformYearStringToNumber } from "@utils/transformYear"; import { yearRangeValidators } from "@utils/yearRangeValidators"; -import { CheckboxComponent } from "../../../ui/components/checkbox/checkbox.component"; import { User } from "@auth/models/user.model"; import { SwitchComponent } from "@ui/components/switch/switch.component"; +import { generateYearList } from "@utils/generate-year-list"; dayjs.extend(cpf); @@ -372,7 +371,7 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { ctl?.setValue(!ctl.value); } - readonly yearListEducation = yearList; + readonly yearListEducation = generateYearList(55); readonly educationStatusList = educationUserType; @@ -975,8 +974,4 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { toggleSpecsGroupsModal(): void { this.specsGroupsModalOpen.update(open => !open); } - - onBack() { - this.location.back(); - } } diff --git a/projects/social_platform/src/app/office/program/detail/shared/news-card/news-card.component.html b/projects/social_platform/src/app/office/program/detail/shared/news-card/news-card.component.html index b2be5a572..d7b88b035 100644 --- a/projects/social_platform/src/app/office/program/detail/shared/news-card/news-card.component.html +++ b/projects/social_platform/src/app/office/program/detail/shared/news-card/news-card.component.html @@ -11,7 +11,7 @@ }
    - {{ newsItem.datetimeCreated | dayjs: "format":"DD MMMM, HH:mm" }} + {{ newsItem.datetimeCreated | dayjs: "format":"DD MMMM YYYY, HH:mm" }}
    diff --git a/projects/social_platform/src/app/office/shared/news-card/news-card.component.html b/projects/social_platform/src/app/office/shared/news-card/news-card.component.html index 54f4d2802..c71d5e263 100644 --- a/projects/social_platform/src/app/office/shared/news-card/news-card.component.html +++ b/projects/social_platform/src/app/office/shared/news-card/news-card.component.html @@ -10,7 +10,7 @@
    {{ feedItem.name }}
    - {{ feedItem.datetimeCreated | dayjs: "format":"DD MMMM, HH:mm" }} + {{ feedItem.datetimeCreated | dayjs: "format":"DD MMMM YYYY, HH:mm" }}
    diff --git a/projects/social_platform/src/app/utils/generate-year-list.ts b/projects/social_platform/src/app/utils/generate-year-list.ts new file mode 100644 index 000000000..597bbb1ca --- /dev/null +++ b/projects/social_platform/src/app/utils/generate-year-list.ts @@ -0,0 +1,48 @@ +/** @format */ + +interface yearListElement { + id: number; // порядковый номер в массиве + value: string; // строка, которую будем показывать в UI + label: string; // то же самое, что и value (можно использовать обе подписи) +} + +/** + * Генерирует массив годов. + * + * @param amount – сколько лет нужно вывести. + * Считаем, что список должен начинаться с «текущий‑year‑amount+1» и + * заканчиваться текущим календарным годом. + * @returns массив объектов вида { id, value, label } + * + * Пример: generateYearList(3) (при текущем 2025‑м году) → + * [ + * { id: 0, value: '2023 год', label: '2023 год' }, + * { id: 1, value: '2024 год', label: '2024 год' }, + * { id: 2, value: '2025 год', label: '2025 год' } + * ] + */ +export const generateYearList = (amount: number): yearListElement[] => { + if (amount <= 0) return []; + + const now = new Date().getFullYear(); + const firstYear = now - amount + 1; + const list: yearListElement[] = []; + + for (let i = 0; i < amount; i++) { + const year = firstYear + i; + list.push({ + id: i, + value: `${year} год`, + label: `${year} год`, + }); + } + + const currentId = amount - 1; + list.push({ + id: currentId, + value: `${now} год`, + label: "по наст. вр.", + }); + + return list; +}; From 7d89a0e78975217eedcde9a37c983004ad5629c5 Mon Sep 17 00:00:00 2001 From: Awakich Date: Tue, 19 Aug 2025 11:55:41 +0300 Subject: [PATCH 007/126] add deletion project in edit & remove achievements block --- .../detail/profile-detail.component.html | 4 +- .../office/projects/edit/edit.component.html | 3 + .../office/projects/edit/edit.component.ts | 15 +++++ .../project-card/project-card.component.html | 67 ++++++++++--------- .../project-card/project-card.component.scss | 15 ++++- 5 files changed, 67 insertions(+), 37 deletions(-) diff --git a/projects/social_platform/src/app/office/profile/detail/profile-detail.component.html b/projects/social_platform/src/app/office/profile/detail/profile-detail.component.html index 66f38f82c..0c06a74d5 100644 --- a/projects/social_platform/src/app/office/profile/detail/profile-detail.component.html +++ b/projects/social_platform/src/app/office/profile/detail/profile-detail.component.html @@ -133,12 +133,12 @@

    {{ user.firstName }} {{ user.lastName }}

    } } -
    + diff --git a/projects/social_platform/src/app/office/projects/edit/edit.component.html b/projects/social_platform/src/app/office/projects/edit/edit.component.html index d5a3af535..dc1c172ed 100644 --- a/projects/social_platform/src/app/office/projects/edit/edit.component.html +++ b/projects/social_platform/src/app/office/projects/edit/edit.component.html @@ -54,6 +54,9 @@ }
    + + Удалить проект + { + this.router.navigateByUrl(`/office/projects/my`); + }, + }); + } + /** * Сохранение проекта как опубликованного с проверкой доп. полей */ diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.html b/projects/social_platform/src/app/office/shared/project-card/project-card.component.html index 05b37125d..6adf340c5 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.html +++ b/projects/social_platform/src/app/office/shared/project-card/project-card.component.html @@ -7,43 +7,46 @@
    }
    -
    -
    -

    {{ project.name }}

    - @if (industryService.industries | async; as industries) { -

    - @if (industryService.getIndustry(industries, project.industry); as industry) { - - {{ industry ? industry.name : "Error" }} - +

    +
    +
    +

    {{ project.name }}

    + @if (industryService.industries | async; as industries) { +

    + @if (industryService.getIndustry(industries, project.industry); as industry) { + + {{ industry ? industry.name : "Error" }} + + } +

    } -

    - } -
    - -
    -

    {{ project.shortDescription }}

    -
    -
    - @if (isSubscribed) { -
    -
    - } @if (project.isCompany) { -
    -
    -

    Компания

    -
    - } +
    +

    {{ project.shortDescription }}

    +
    +
    + @if (isSubscribed) { +
    + +
    + } @if (project.isCompany) { +
    +
    +

    Компания

    +
    + } +
    - + +
    +
    @if (project.draft) {

    Черновик

    diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss b/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss index 751048c6f..576d8d90a 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss +++ b/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss @@ -11,6 +11,9 @@ padding: 24px 15px; background-color: var(--white); border-radius: 15px; + display: flex; + flex-direction: column; + justify-content: space-between; @include responsive.apply-desktop { border: 1px solid var(--medium-grey-for-outline); @@ -41,6 +44,12 @@ border-radius: 15px 15px 0 0; } + &__content { + flex-grow: 1; + display: flex; + flex-direction: column; + } + &__head { display: flex; align-items: center; @@ -70,7 +79,7 @@ display: flex; gap: 15px; align-items: center; - margin-top: 22px; + margin-top: 15px; color: var(--dark-grey); &--single { @@ -128,10 +137,10 @@ display: flex; align-items: center; justify-content: space-between; - margin-top: 16px; + margin-top: auto; &:empty { - margin-top: 0; + margin-top: auto; } } } From 6cb1d8c4eb8ed55e0dbb3c119ea9747330530816 Mon Sep 17 00:00:00 2001 From: Awakich Date: Tue, 19 Aug 2025 11:56:10 +0300 Subject: [PATCH 008/126] add styles for project card --- .../shared/project-card/project-card.component.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss b/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss index 576d8d90a..7fb742dda 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss +++ b/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss @@ -7,13 +7,13 @@ flex-direction: column; &__body { + display: flex; + flex-direction: column; + justify-content: space-between; height: 200px; padding: 24px 15px; background-color: var(--white); border-radius: 15px; - display: flex; - flex-direction: column; - justify-content: space-between; @include responsive.apply-desktop { border: 1px solid var(--medium-grey-for-outline); @@ -45,9 +45,9 @@ } &__content { - flex-grow: 1; display: flex; flex-direction: column; + flex-grow: 1; } &__head { From 1836a6ba69ddf4fd5bd62ca320797710f8920f9c Mon Sep 17 00:00:00 2001 From: Awakich Date: Tue, 19 Aug 2025 16:19:16 +0300 Subject: [PATCH 009/126] add team members block in edit project --- .../app/office/models/collaborator.model.ts | 10 +++- .../src/app/office/models/project.model.ts | 11 ++++- .../src/app/office/office.component.scss | 2 +- .../detail/profile-detail.component.scss | 2 +- .../detail/projects/projects.component.html | 18 ++++++-- .../office/projects/edit/edit.component.ts | 1 + .../edit/services/project-team.service.ts | 26 +++++++---- .../project-team-step.component.html | 23 +++++++++- .../project-team-step.component.ts | 11 ++++- .../collaborator-card.component.html | 22 +++++++++ .../collaborator-card.component.scss | 33 +++++++++++++ .../collaborator-card.component.spec.ts | 26 +++++++++++ .../collaborator-card.component.ts | 46 +++++++++++++++++++ .../invite-card/invite-card.component.ts | 3 +- .../app/office/shared/nav/nav.component.html | 2 +- 15 files changed, 212 insertions(+), 24 deletions(-) create mode 100644 projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.html create mode 100644 projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.scss create mode 100644 projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.spec.ts create mode 100644 projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.ts diff --git a/projects/social_platform/src/app/office/models/collaborator.model.ts b/projects/social_platform/src/app/office/models/collaborator.model.ts index 40b8f4525..1ee591f65 100644 --- a/projects/social_platform/src/app/office/models/collaborator.model.ts +++ b/projects/social_platform/src/app/office/models/collaborator.model.ts @@ -15,7 +15,15 @@ export class Collaborator { /** Роль участника в проекте (например, "Разработчик", "Дизайнер") */ role!: string; /** Массив ключевых навыков участника */ - keySkills!: string[]; + skills!: { + id: number; + name: string; + category: { + id: number; + name: string; + }; + }[]; + /** URL аватара участника */ avatar!: string; } diff --git a/projects/social_platform/src/app/office/models/project.model.ts b/projects/social_platform/src/app/office/models/project.model.ts index b3b236c25..4745c4b1c 100644 --- a/projects/social_platform/src/app/office/models/project.model.ts +++ b/projects/social_platform/src/app/office/models/project.model.ts @@ -106,6 +106,15 @@ const collaborator = { lastName: "string", userId: 0, avatar: "string", - keySkills: ["angular"], + skills: [ + { + id: 309, + name: "Python", + category: { + id: 7, + name: "Back-end", + }, + }, + ], role: "Front-end", }; diff --git a/projects/social_platform/src/app/office/office.component.scss b/projects/social_platform/src/app/office/office.component.scss index be49149e3..86d90ca01 100644 --- a/projects/social_platform/src/app/office/office.component.scss +++ b/projects/social_platform/src/app/office/office.component.scss @@ -88,7 +88,7 @@ transition: color 0.2s; &:hover { - color: var(--black); + color: var(--accent); } &__name { diff --git a/projects/social_platform/src/app/office/profile/detail/profile-detail.component.scss b/projects/social_platform/src/app/office/profile/detail/profile-detail.component.scss index 7c78d34b0..ae0ff3ecd 100644 --- a/projects/social_platform/src/app/office/profile/detail/profile-detail.component.scss +++ b/projects/social_platform/src/app/office/profile/detail/profile-detail.component.scss @@ -180,7 +180,7 @@ $detail-bar-mb: 12px; transform: translateX(-50%); @include responsive.apply-desktop { - top: 50%; + top: 10%; bottom: auto; left: 21px; transform: translate(0, -50%); diff --git a/projects/social_platform/src/app/office/profile/detail/projects/projects.component.html b/projects/social_platform/src/app/office/profile/detail/projects/projects.component.html index 5de149c7d..baf44d358 100644 --- a/projects/social_platform/src/app/office/profile/detail/projects/projects.component.html +++ b/projects/social_platform/src/app/office/profile/detail/projects/projects.component.html @@ -1,9 +1,9 @@ - @if (user | async; as user) {
    @if (loggedUserId | async; as loggedUserId) {
    + @if (user.projects.length) {

    {{ @@ -16,12 +16,13 @@

    @for (p of user.projects; track p.id) {
  • - +
  • }

+ } @if (subs | async; as subs) { @if (subs.length) {

{{ @@ -30,18 +31,25 @@

: "Проекты, на которые подписан " + user.firstName }}

- @if (subs | async; as subs) {
    @for (p of subs; track p.id) {
  • - +
  • }
- }
+ } } @if (!user.projects.length) { @if (subs | async; as subs) { @if (!subs.length) { +

+ Вы пока не состоите ни в одном проекте и не подписаны ни на один. +

+ } } @else { +

+ Вы пока не состоите ни в одном проекте и не подписаны ни на один. +

+ } } } 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 e0ee5fee8..a0f43e0c5 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 @@ -576,6 +576,7 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { // Используем сервис для инициализации данных проекта this.projectFormService.initializeProjectData(project); this.projectTeamService.setInvites(invites); + this.projectTeamService.setCollaborators(project.collaborators); // Инициализируем дополнительные поля через сервис if (project.partnerProgram) { diff --git a/projects/social_platform/src/app/office/projects/edit/services/project-team.service.ts b/projects/social_platform/src/app/office/projects/edit/services/project-team.service.ts index f3578d20a..becc9e491 100644 --- a/projects/social_platform/src/app/office/projects/edit/services/project-team.service.ts +++ b/projects/social_platform/src/app/office/projects/edit/services/project-team.service.ts @@ -3,6 +3,7 @@ import { computed, inject, Injectable, signal } from "@angular/core"; import { FormBuilder, FormGroup, Validators } from "@angular/forms"; import { ValidationService } from "@corelib"; +import { Collaborator } from "@office/models/collaborator.model"; import { Invite } from "@office/models/invite.model"; import { InviteService } from "@services/invite.service"; @@ -19,6 +20,7 @@ export class ProjectTeamService { private readonly validationService = inject(ValidationService); public readonly invites = signal([]); + public readonly collaborators = signal([]); public readonly isInviteModalOpen = signal(false); public readonly inviteNotExistingError = signal(null); @@ -63,6 +65,22 @@ export class ProjectTeamService { this.invites.set(invites); } + /** + * Устанавливает список команды + * @param collaborators массив Collaborator + */ + public setCollaborators(collaborators: Collaborator[]): void { + this.collaborators.set(collaborators); + } + + /** + * Возвращает текущий список команды. + * @returns Collaborator[] массив команды + */ + public getCollaborators(): Collaborator[] { + return this.collaborators(); + } + /** * Возвращает текущий список приглашений. * @returns Invite[] массив приглашений @@ -84,14 +102,6 @@ export class ProjectTeamService { return this.inviteForm.get("specialization"); } - /** - * Проверяет, заполнены ли все приглашения (accepted === null). - * @returns boolean true если все приглашения приняты или отклонены - */ - public readonly invitesFill = computed( - () => this.invites().length > 0 && this.invites().every(inv => inv.isAccepted === null) - ); - /** * Открывает модальное окно для отправки приглашения. */ diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.html b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.html index b7eb449b8..77ae94750 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.html +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.html @@ -5,8 +5,27 @@
    + @for (collaborator of collaborators; track collaborator.userId) { +
  • + +
  • + } +
+ + +
    @for (user of invites; track user.id) { @if (user.isAccepted === null) {
  • diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.ts b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.ts index 82c44d127..9e0ccbe42 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.ts +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.ts @@ -12,6 +12,7 @@ import { ProjectTeamService } from "../../services/project-team.service"; import { rolesMembersList } from "projects/core/src/consts/list-roles-members"; import { ActivatedRoute } from "@angular/router"; import { IconComponent } from "@uilib"; +import { CollaboratorCardComponent } from "@office/shared/collaborator-card/collaborator-card.component"; @Component({ selector: "app-project-team-step", @@ -28,6 +29,7 @@ import { IconComponent } from "@uilib"; ControlErrorPipe, InviteCardComponent, ModalComponent, + CollaboratorCardComponent, ], }) export class ProjectTeamStepComponent implements OnInit { @@ -41,6 +43,7 @@ export class ProjectTeamStepComponent implements OnInit { ngOnInit(): void { this.projectTeamService.setInvites(this.invites); + this.projectTeamService.setCollaborators(this.collaborators); // Настраиваем динамическую валидацию this.projectTeamService.setupDynamicValidation(); @@ -68,8 +71,12 @@ export class ProjectTeamStepComponent implements OnInit { return this.projectTeamService.getInvites(); } - get invitesFill() { - return this.projectTeamService.invitesFill; + get collaborators() { + return this.projectTeamService.getCollaborators(); + } + + get invitesFill(): boolean { + return this.invites.some(inv => inv.isAccepted === null); } get isInviteModalOpen() { diff --git a/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.html b/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.html new file mode 100644 index 000000000..8b4fb548e --- /dev/null +++ b/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.html @@ -0,0 +1,22 @@ + + +@if (collaborator) { +
    +
    +

    + {{ collaborator.firstName }} {{ collaborator.lastName }} +

    + +

    + @for (skill of collaborator.skills; track $index) { +

    +

    Категория - {{ skill.category.name }}

    +

    Навык - {{ skill.name }}

    +
    + } +

    + +

    Роль - {{ collaborator.role }}

    +
    +
    +} diff --git a/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.scss b/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.scss new file mode 100644 index 000000000..95d0eb155 --- /dev/null +++ b/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.scss @@ -0,0 +1,33 @@ +/** @format */ + +@use "styles/typography"; + +.collaborator { + display: flex; + align-items: flex-start; + justify-content: space-between; + padding: 15px 10px; + background-color: var(--light-gray); + border-radius: var(--rounded-md); + + &__role { + color: var(--black); + } + + &__requirements { + color: var(--dark-grey); + } + + &__info { + display: flex; + flex-direction: column; + gap: 5px; + } + + &__skills { + display: flex; + flex-direction: column; + gap: 10px; + margin: 10px 0; + } +} diff --git a/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.spec.ts b/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.spec.ts new file mode 100644 index 000000000..a14a0ba25 --- /dev/null +++ b/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.spec.ts @@ -0,0 +1,26 @@ +/** @format */ + +import { ComponentFixture, TestBed } from "@angular/core/testing"; + +import { InviteCardComponent } from "./collaborator-card.component"; + +describe("VacancyCardComponent", () => { + let component: InviteCardComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [InviteCardComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(InviteCardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.ts b/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.ts new file mode 100644 index 000000000..87d715b6c --- /dev/null +++ b/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.ts @@ -0,0 +1,46 @@ +/** @format */ + +import { CommonModule } from "@angular/common"; +import { Component, Input, OnInit } from "@angular/core"; +import { FormBuilder, FormGroup, ReactiveFormsModule } from "@angular/forms"; +import { ErrorMessage } from "@error/models/error-message"; +import { Collaborator } from "@office/models/collaborator.model"; + +/** + * Компонент карточки участника команды или проект + * + * Функциональность: + * - Отображает информацию о участнике (роль, специализация) + * + * Входные параметры: + * @Input invite - объект участника (обязательный) + */ +@Component({ + selector: "app-collaborator-card", + templateUrl: "./collaborator-card.component.html", + styleUrl: "./collaborator-card.component.scss", + standalone: true, + imports: [CommonModule, ReactiveFormsModule], +}) +export class CollaboratorCardComponent implements OnInit { + constructor(private readonly fb: FormBuilder) { + this.inviteForm = this.fb.group({ + role: [""], + specializations: this.fb.array([]), + }); + } + + inviteForm: FormGroup; + errorMessage = ErrorMessage; + + @Input({ required: true }) collaborator!: Collaborator; + + ngOnInit(): void { + if (this.collaborator) { + this.inviteForm.patchValue({ + role: this.collaborator.role, + specialization: this.collaborator.skills, + }); + } + } +} diff --git a/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.ts b/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.ts index 383c54f6f..da0c96cc4 100644 --- a/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.ts +++ b/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.ts @@ -20,7 +20,7 @@ import { rolesMembersList } from "projects/core/src/consts/list-roles-members"; * * Входные параметры: * @Input invite - объект приглашения (обязательный) - * @Input type - тип приглашения: "team" или "members" (по умолчанию "members") + * @Input type - тип приглашения: "team" или "invite" (по умолчанию "invite") * * Выходные события: * @Output remove - событие удаления приглашения, передает ID приглашения @@ -55,7 +55,6 @@ export class InviteCardComponent implements OnInit { errorMessage = ErrorMessage; @Input({ required: true }) invite!: Invite; - @Input() type: "team" | "members" = "members"; @Output() remove = new EventEmitter(); @Output() edit = new EventEmitter<{ inviteId: number; role: string; specialization: string }>(); diff --git a/projects/social_platform/src/app/office/shared/nav/nav.component.html b/projects/social_platform/src/app/office/shared/nav/nav.component.html index d795304a6..877993e27 100644 --- a/projects/social_platform/src/app/office/shared/nav/nav.component.html +++ b/projects/social_platform/src/app/office/shared/nav/nav.component.html @@ -83,7 +83,7 @@

    {{ title }}

    - Навыки + Траектории
    PRO
    From 0322694d3ed4a48ece9d157e8284eda4918024a5 Mon Sep 17 00:00:00 2001 From: Awakich Date: Thu, 21 Aug 2025 14:16:37 +0300 Subject: [PATCH 010/126] fix approve skills in profile & add validators for password --- .../src/lib/services/validation.service.ts | 124 +++++++++++++++++- .../app/auth/models/password-errors.model.ts | 14 ++ .../app/auth/register/register.component.html | 32 ++++- .../app/auth/register/register.component.ts | 2 +- .../profile/detail/main/main.component.html | 17 ++- .../profile/detail/main/main.component.scss | 1 + .../profile/detail/main/main.component.ts | 33 ++++- .../detail/profile-detail.component.scss | 2 +- .../app/office/profile/edit/edit.component.ts | 3 +- .../collaborator-card.component.html | 8 +- 10 files changed, 208 insertions(+), 28 deletions(-) create mode 100644 projects/social_platform/src/app/auth/models/password-errors.model.ts diff --git a/projects/core/src/lib/services/validation.service.ts b/projects/core/src/lib/services/validation.service.ts index bc42943d9..ceff06c13 100644 --- a/projects/core/src/lib/services/validation.service.ts +++ b/projects/core/src/lib/services/validation.service.ts @@ -2,6 +2,7 @@ import { Injectable } from "@angular/core"; import { AbstractControl, FormGroup, ValidationErrors, ValidatorFn } from "@angular/forms"; +import { PasswordValidationErrors } from "@auth/models/password-errors.model"; import * as dayjs from "dayjs"; import * as cpf from "dayjs/plugin/customParseFormat"; import * as relativeTime from "dayjs/plugin/relativeTime"; @@ -58,7 +59,6 @@ export class ValidationService { return group => { const controls = [group.get(left), group.get(right)]; - // Проверяем существование контролов if (!controls.every(Boolean)) { throw new Error(`No control with name ${left} or ${right}`); } @@ -66,16 +66,13 @@ export class ValidationService { const isMatching = controls[0]?.value === controls[1]?.value; if (!isMatching) { - // Устанавливаем ошибку для обоих контролов controls.forEach(c => c?.setErrors({ ...(c.errors || {}), unMatch: true })); return { unMatch: true }; } - // Удаляем ошибку если поля совпадают controls.forEach(c => { if (c?.errors) { delete c.errors?.["unMatch"]; - // Если других ошибок нет, очищаем errors полностью if (!Object.keys(c.errors).length) { c.setErrors(null); } @@ -86,6 +83,123 @@ export class ValidationService { }; } + /** + * Валидатор для проверки силы пароля + * + * Требования к паролю: + * - Минимум 8 символов + * - Минимум одна заглавная буква (A-Z) + * - Минимум одна строчная буква (a-z) + * - Минимум одна цифра (0-9) + * - Минимум один специальный символ (!@#$%^&*()_+-=[]{}|;:,.<>?) + * - Не должен содержать пробелы + * - Не должен содержать последовательности типа "123456" или "abcdef" + * - Не должен содержать повторяющиеся символы более 2 раз подряд + */ + usePasswordValidator(minLength = 6): ValidatorFn { + return (control: AbstractControl): PasswordValidationErrors | null => { + const value: string = control.value; + + if (!value) { + return null; + } + + const errors: PasswordValidationErrors = {}; + + if (value.length < minLength) { + errors.passwordTooShort = { + requiredLength: minLength, + actualLength: value.length, + }; + } + + if (!/[A-Z]/.test(value)) { + errors.passwordNoUppercase = { + message: "Пароль должен содержать минимум одну заглавную букву", + }; + } + + if (!/[a-z]/.test(value)) { + errors.passwordNoLowercase = { + message: "Пароль должен содержать минимум одну строчную букву", + }; + } + + if (!/[0-9]/.test(value)) { + errors.passwordNoNumber = { + message: "Пароль должен содержать минимум одну цифру", + }; + } + + if (!/[!@#$%^&*()_+\-=[\]{}|;:,.<>?]/.test(value)) { + errors.passwordNoSpecialChar = { + message: "Пароль должен содержать минимум один специальный символ (!@#$%^&* и т.д.)", + }; + } + + if (/\s/.test(value)) { + errors.passwordHasSpaces = { + message: "Пароль не должен содержать пробелы", + }; + } + + if (this.hasSequence(value)) { + errors.passwordHasSequence = { + message: "Пароль не должен содержать последовательности символов", + }; + } + + if (this.hasRepeatingChars(value)) { + errors.passwordHasRepeating = { + message: "Пароль не должен содержать более 2 одинаковых символов подряд", + }; + } + + return Object.keys(errors).length > 0 ? errors : null; + }; + } + + /** + * Проверяет наличие последовательностей в пароле + */ + private hasSequence(password: string): boolean { + const sequences = [ + "01234567890", + "09876543210", + "abcdefghijklmnopqrstuvwxyz", + "zyxwvutsrqponmlkjihgfedcba", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "ZYXWVUTSRQPONMLKJIHGFEDCBA", + "qwertyuiopasdfghjklzxcvbnm", + "йцукенгшщзхъфывапролджэячсмитьбю", + ]; + + const lowerPassword = password.toLowerCase(); + + for (const sequence of sequences) { + for (let i = 0; i <= sequence.length - 4; i++) { + const subSequence = sequence.substring(i, i + 4); + if (lowerPassword.includes(subSequence)) { + return true; + } + } + } + + return false; + } + + /** + * Проверяет наличие более 2 повторяющихся символов подряд + */ + private hasRepeatingChars(password: string): boolean { + for (let i = 0; i < password.length - 2; i++) { + if (password[i] === password[i + 1] && password[i + 1] === password[i + 2]) { + return true; + } + } + return false; + } + /** * Валидатор для проверки формата даты DD.MM.YYYY * @param control - Контрол формы для валидации @@ -101,10 +215,8 @@ export class ValidationService { */ useDateFormatValidator(control: AbstractControl): ValidationErrors | null { try { - // Строгая проверка формата DD.MM.YYYY const value = dayjs(control.value, "DD.MM.YYYY", true); - // Проверяем что дата не в будущем и валидна if (control.value && (value.fromNow().includes("in") || !value.isValid())) { return { invalidDateFormat: true }; } diff --git a/projects/social_platform/src/app/auth/models/password-errors.model.ts b/projects/social_platform/src/app/auth/models/password-errors.model.ts new file mode 100644 index 000000000..686338022 --- /dev/null +++ b/projects/social_platform/src/app/auth/models/password-errors.model.ts @@ -0,0 +1,14 @@ +/** @format */ + +import { ValidationErrors } from "@angular/forms"; + +export interface PasswordValidationErrors extends ValidationErrors { + passwordTooShort?: { requiredLength: number; actualLength: number }; + passwordNoUppercase?: { message: string }; + passwordNoLowercase?: { message: string }; + passwordNoNumber?: { message: string }; + passwordNoSpecialChar?: { message: string }; + passwordHasSpaces?: { message: string }; + passwordHasSequence?: { message: string }; + passwordHasRepeating?: { message: string }; +} 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 ee4eea5bb..a6c878c0c 100644 --- a/projects/social_platform/src/app/auth/register/register.component.html +++ b/projects/social_platform/src/app/auth/register/register.component.html @@ -124,12 +124,34 @@

    {{ errorMessage.VALIDATION_REQUIRED }}
    - } @if (password | controlError: "minlength") { + } @if (password | controlError: "passwordTooShort") {
    - @if (password.errors) { - {{ errorMessage.VALIDATION_TOO_SHORT }} - {{ password.errors["minlength"]["requiredLength"] }} - } + @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") {
    diff --git a/projects/social_platform/src/app/auth/register/register.component.ts b/projects/social_platform/src/app/auth/register/register.component.ts index 7e426b1b6..ac8e3f88b 100644 --- a/projects/social_platform/src/app/auth/register/register.component.ts +++ b/projects/social_platform/src/app/auth/register/register.component.ts @@ -73,7 +73,7 @@ export class RegisterComponent implements OnInit { "", [Validators.required, Validators.email, this.validationService.useEmailValidator()], ], - password: ["", [Validators.required, Validators.minLength(6)]], + password: ["", [Validators.required, this.validationService.usePasswordValidator(6)]], repeatedPassword: ["", [Validators.required]], }, { validators: [this.validationService.useMatchValidator("password", "repeatedPassword")] } diff --git a/projects/social_platform/src/app/office/profile/detail/main/main.component.html b/projects/social_platform/src/app/office/profile/detail/main/main.component.html index 1778d044c..98298f5c5 100644 --- a/projects/social_platform/src/app/office/profile/detail/main/main.component.html +++ b/projects/social_platform/src/app/office/profile/detail/main/main.component.html @@ -85,12 +85,12 @@

    Обо мне

    - + {{ skill.name }} @@ -451,4 +451,15 @@

    Контакты

    } + + + + } diff --git a/projects/social_platform/src/app/office/profile/detail/main/main.component.scss b/projects/social_platform/src/app/office/profile/detail/main/main.component.scss index a7b00c771..242a3463f 100644 --- a/projects/social_platform/src/app/office/profile/detail/main/main.component.scss +++ b/projects/social_platform/src/app/office/profile/detail/main/main.component.scss @@ -518,6 +518,7 @@ $section-padding: 24px; gap: 80px; align-items: center; justify-content: space-between; + width: 100%; } &__left { diff --git a/projects/social_platform/src/app/office/profile/detail/main/main.component.ts b/projects/social_platform/src/app/office/profile/detail/main/main.component.ts index 757cea152..6a794bc42 100644 --- a/projects/social_platform/src/app/office/profile/detail/main/main.component.ts +++ b/projects/social_platform/src/app/office/profile/detail/main/main.component.ts @@ -29,6 +29,7 @@ import { ProfileService } from "@auth/services/profile.service"; import { ModalComponent } from "@ui/components/modal/modal.component"; import { AvatarComponent } from "../../../../ui/components/avatar/avatar.component"; import { Skill } from "@office/models/skill"; +import { HttpErrorResponse } from "@angular/common/http"; /** * Главный компонент страницы профиля пользователя @@ -148,6 +149,8 @@ export class ProfileMainComponent implements OnInit, AfterViewInit, OnDestroy { readAllWorkExperience = false; readAllModal = false; + approveOwnSkillModal = false; + @ViewChild(NewsFormComponent) newsFormComponent?: NewsFormComponent; @ViewChild(NewsCardComponent) newsCardComponent?: NewsCardComponent; @@ -236,13 +239,17 @@ export class ProfileMainComponent implements OnInit, AfterViewInit, OnDestroy { * @param event - событие клика для предотвращения всплытия * @param skill - объект навыка для обновления */ - onToggleApprove(skillId: number, event: Event, skill: Skill) { + onToggleApprove(skillId: number, event: Event, skill: Skill, profileId: number) { event.stopPropagation(); const userId = this.route.snapshot.params["id"]; - if (skill.approves.length > 0) { + const isApprovedByCurrentUser = skill.approves.some(approve => { + return approve.confirmedBy.id === profileId; + }); + + if (isApprovedByCurrentUser) { this.profileApproveSkillService.unApproveSkill(userId, skillId).subscribe(() => { - skill.approves = skill.approves.slice(0, -1); + skill.approves = skill.approves.filter(approve => approve.confirmedBy.id !== profileId); this.cdRef.markForCheck(); }); } else { @@ -260,13 +267,27 @@ export class ProfileMainComponent implements OnInit, AfterViewInit, OnDestroy { ) ) ) - .subscribe(updatedApprove => { - skill.approves = [...skill.approves, updatedApprove]; - this.cdRef.markForCheck(); + .subscribe({ + next: updatedApprove => { + skill.approves = [...skill.approves, updatedApprove]; + this.cdRef.markForCheck(); + }, + error: err => { + if (err instanceof HttpErrorResponse) { + if (err.status === 400) { + this.approveOwnSkillModal = true; + this.cdRef.markForCheck(); + } + } + }, }); } } + isUserApproveSkill(skill: Skill, profileId: number): boolean { + return skill.approves.some(approve => approve.confirmedBy.id === profileId); + } + openSkills: any = {}; /** diff --git a/projects/social_platform/src/app/office/profile/detail/profile-detail.component.scss b/projects/social_platform/src/app/office/profile/detail/profile-detail.component.scss index ae0ff3ecd..bcd773f6a 100644 --- a/projects/social_platform/src/app/office/profile/detail/profile-detail.component.scss +++ b/projects/social_platform/src/app/office/profile/detail/profile-detail.component.scss @@ -31,7 +31,7 @@ $detail-bar-mb: 12px; display: flex; flex-direction: column; flex-grow: 1; - gap: 20px; + gap: 10px; max-height: calc(100% - #{$detail-bar-height} - #{$detail-bar-mb}); padding-bottom: 12px; } diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.ts b/projects/social_platform/src/app/office/profile/edit/edit.component.ts index 70d804796..ee76d2ec3 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.ts +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.ts @@ -107,8 +107,7 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { private readonly skillsService: SkillsService, private readonly router: Router, private readonly route: ActivatedRoute, - private readonly navService: NavService, - private readonly location: Location + private readonly navService: NavService ) { this.profileForm = this.fb.group({ firstName: ["", [Validators.required]], diff --git a/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.html b/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.html index 8b4fb548e..e3f6c1dd2 100644 --- a/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.html +++ b/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.html @@ -7,14 +7,14 @@

    {{ collaborator.firstName }} {{ collaborator.lastName }}

    -

    +

    @for (skill of collaborator.skills; track $index) { -
    +

    Категория - {{ skill.category.name }}

    Навык - {{ skill.name }}

    -
    +
    } -

    +

    Роль - {{ collaborator.role }}

    From 7ad893e08293581c4c995d717f76ad897b48b7a3 Mon Sep 17 00:00:00 2001 From: Awakich Date: Fri, 22 Aug 2025 13:15:37 +0300 Subject: [PATCH 011/126] remove linking block for duplicated project --- .../office/projects/edit/services/project-form.service.ts | 4 +++- .../project-main-step/project-main-step.component.html | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) 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 7ab12e770..4c207a8b3 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 @@ -109,9 +109,11 @@ export class ProjectFormService { problem: project.problem ?? "", presentationAddress: project.presentationAddress, coverImageAddress: project.coverImageAddress, - partnerProgramId: project.partnerProgramId ?? null, + partnerProgramId: project.partnerProgram?.programId ?? null, }); + console.log(project.partnerProgram?.programId); + if (project.partnerProgram) { this.relationId.set(project.partnerProgram?.programLinkId); } 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 f4b14f355..c59ef5c9c 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 @@ -182,8 +182,8 @@
    - @if (authService.profile | async; as profile) { @if (profile.id == leaderId) { @if - (programTagsOptions.length) { + @if (!partnerProgramId?.value) { @if (authService.profile | async; as profile) { @if (profile.id + == leaderId) { @if (programTagsOptions.length) {
    Привязать проект к программе - } } } @if (presentationAddress; as presentationAddress) { + } } } } @if (presentationAddress; as presentationAddress) {
    Date: Fri, 22 Aug 2025 19:29:19 +0300 Subject: [PATCH 012/126] fix loading of projects with search --- .../profile/detail/main/main.component.html | 19 +++---- .../profile/detail/main/main.component.scss | 22 ++++++++ .../office/projects/edit/edit.component.html | 1 - .../office/projects/edit/edit.component.ts | 42 ++++++--------- .../project-main-step.component.ts | 1 - .../office/projects/list/list.component.html | 4 +- .../office/projects/list/list.component.ts | 53 ++++++++----------- .../projects-filter.component.ts | 3 +- .../app/office/projects/projects.component.ts | 2 +- .../office/vacancies/list/list.component.ts | 6 +-- 10 files changed, 77 insertions(+), 76 deletions(-) diff --git a/projects/social_platform/src/app/office/profile/detail/main/main.component.html b/projects/social_platform/src/app/office/profile/detail/main/main.component.html index 98298f5c5..abadc1600 100644 --- a/projects/social_platform/src/app/office/profile/detail/main/main.component.html +++ b/projects/social_platform/src/app/office/profile/detail/main/main.component.html @@ -257,9 +257,9 @@

    Образование

    - +

    {{ education.organizationName }} - +

    {{ education.description }}

    @@ -315,9 +315,9 @@

    Работа

    - +

    {{ workExperience.organizationName }} - +

    {{ workExperience.description }}

    @@ -340,7 +340,7 @@

    Языки

      - @for (p of user.userLanguages.slice(0, 3); track $index) { + @for (p of user.userLanguages.slice(0, 2); track $index) {
    • Языки

  • }
-
- @if (user.userLanguages) { +
+ @if (user.userLanguages.length > 2) {
    - @for (userLanguagesItem of user.userLanguages.slice(3); track $index) { + @for (userLanguagesItem of user.userLanguages.slice(2); track $index) {
  • Языки

{{ userLanguages.language }} {{ userLanguages.languageLevel }}

- @if (userLanguagesLength > 3) { + @if (userLanguagesLength > 2) {
{{ readAllLanguages ? "Скрыть" : "Читать полностью" }}
@@ -457,6 +457,7 @@

Контакты

diff --git a/projects/social_platform/src/app/office/profile/detail/main/main.component.scss b/projects/social_platform/src/app/office/profile/detail/main/main.component.scss index 242a3463f..6d73578e4 100644 --- a/projects/social_platform/src/app/office/profile/detail/main/main.component.scss +++ b/projects/social_platform/src/app/office/profile/detail/main/main.component.scss @@ -30,6 +30,28 @@ $section-padding: 24px; } } +.languages { + &__title { + margin-bottom: 12px; + color: var(--black); + } + + ul { + overflow: hidden; + + span { + overflow: hidden; + text-overflow: ellipsis; + } + } + + li:not(:last-child) { + margin-bottom: 12px; + } + + @include expandable-list; +} + .main { display: flex; flex-direction: column; diff --git a/projects/social_platform/src/app/office/projects/edit/edit.component.html b/projects/social_platform/src/app/office/projects/edit/edit.component.html index dc1c172ed..7cc595819 100644 --- a/projects/social_platform/src/app/office/projects/edit/edit.component.html +++ b/projects/social_platform/src/app/office/projects/edit/edit.component.html @@ -24,7 +24,6 @@ [leaderId]="leaderId" [projSubmitInitiated]="projSubmitInitiated" (assignToProgram)="assignProjectToProgram()" - (saveProject)="onSaveProject($event)" > } @else if (editingStep === "contacts") { 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 a0f43e0c5..15a0f5100 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 @@ -294,20 +294,6 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { setProjFormIsSubmitting!: (status: boolean) => void; - /** - * Выполнение сохранения проекта - * Из дочернего компонента project-main-step через emit - * - * @param event тип проекта для публикации или для черновика - */ - onSaveProject(event: { type: "draft" | "published" }): void { - if (event.type === "draft") { - this.saveProjectAsDraft(); - } else { - this.saveProjectAsPublished(); - } - } - /** * Очистка всех ошибок валидации */ @@ -387,26 +373,30 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { }); const payload = this.projectFormService.getFormValue(); + const projectId = Number(this.route.snapshot.paramMap.get("projectId")); + + if (this.vacancyForm.dirty) { + this.projectVacancyService.submitVacancy(projectId); + } if ( !this.validationService.getFormValidation(this.projectForm) || - !this.validationService.getFormValidation(this.additionalForm) + !this.validationService.getFormValidation(this.additionalForm) || + !this.validationService.getFormValidation(this.vacancyForm) ) { return; } this.setProjFormIsSubmitting(true); - this.projectService - .updateProject(Number(this.route.snapshot.paramMap.get("projectId")), payload) - .subscribe({ - next: () => { - this.setProjFormIsSubmitting(false); - this.router.navigateByUrl(`/office/projects/my`); - }, - error: () => { - this.setProjFormIsSubmitting(false); - }, - }); + this.projectService.updateProject(projectId, payload).subscribe({ + next: () => { + this.setProjFormIsSubmitting(false); + this.router.navigateByUrl(`/office/projects/my`); + }, + error: () => { + this.setProjFormIsSubmitting(false); + }, + }); } // Методы для работы с модальными окнами diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.ts b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.ts index 61bf66c06..423e2d334 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.ts +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.ts @@ -44,7 +44,6 @@ export class ProjectMainStepComponent implements OnInit, OnDestroy { @Input() projectId!: number; @Output() assignToProgram = new EventEmitter(); - @Output() saveProject = new EventEmitter<{ type: "draft" | "published" }>(); private subscription = new Subscription(); diff --git a/projects/social_platform/src/app/office/projects/list/list.component.html b/projects/social_platform/src/app/office/projects/list/list.component.html index b58e06bef..ee0f61b1e 100644 --- a/projects/social_platform/src/app/office/projects/list/list.component.html +++ b/projects/social_platform/src/app/office/projects/list/list.component.html @@ -8,7 +8,7 @@ } -
-
- Заработная плата -
- -
- -
+
+ Заработная плата +
+ + + +
+ + +
- +
diff --git a/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.ts b/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.ts index 571a447d4..d02211cbe 100644 --- a/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.ts +++ b/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.ts @@ -16,21 +16,15 @@ import { ActivatedRoute, Router } from "@angular/router"; import { ButtonComponent, CheckboxComponent, IconComponent, InputComponent } from "@ui/components"; import { ClickOutsideModule } from "ng-click-outside"; import { FeedService } from "@office/feed/services/feed.service"; -import { FormBuilder, FormGroup, ReactiveFormsModule } from "@angular/forms"; import { VacancyService } from "@office/services/vacancy.service"; -import { debounceTime, map, Subscription, tap } from "rxjs"; +import { debounceTime, map, Subject, Subscription, tap } from "rxjs"; import { filterExperience } from "projects/core/src/consts/filter-experience"; import { filterWorkFormat } from "projects/core/src/consts/filter-work-format"; import { filterWorkSchedule } from "projects/core/src/consts/filter-work-schedule"; /** - * Компонент фильтра вакансий - * Предоставляет интерфейс для фильтрации вакансий по различным критериям: - * - Опыт работы - * - Формат работы (удаленно/офис/гибрид) - * - График работы - * - Диапазон зарплаты - * Поддерживает как десктопную, так и мобильную версии интерфейса + * Компонент фильтра вакансий без использования реактивных форм + * Использует сигналы для управления состоянием полей зарплаты */ @Component({ selector: "app-vacancy-filter", @@ -42,7 +36,6 @@ import { filterWorkSchedule } from "projects/core/src/consts/filter-work-schedul ClickOutsideModule, IconComponent, InputComponent, - ReactiveFormsModule, ], templateUrl: "./vacancy-filter.component.html", styleUrl: "./vacancy-filter.component.scss", @@ -67,17 +60,7 @@ export class VacancyFilterComponent implements OnInit { /** Сервис для работы с вакансиями */ vacancyService = inject(VacancyService); - /** - * Конструктор компонента - * @param fb - FormBuilder для создания формы зарплатного диапазона - */ - constructor(private readonly fb: FormBuilder) { - // Создание формы для фильтрации по зарплате - this.salaryForm = this.fb.group({ - salaryMin: [""], // Минимальная зарплата - salaryMax: [""], // Максимальная зарплата - }); - } + constructor() {} /** Приватное поле для хранения значения поиска */ private _searchValue: string | undefined; @@ -101,33 +84,12 @@ export class VacancyFilterComponent implements OnInit { /** Событие изменения значения поиска */ @Output() searchValueChange = new EventEmitter(); - /** - * Инициализация компонента - * Подписывается на изменения параметров запроса для синхронизации фильтров - */ - ngOnInit() { - this.salaryForm.valueChanges.pipe(debounceTime(300)).subscribe(vacancyFormValues => { - console.log(vacancyFormValues.salary_max); - }); - - // Подписка на изменения параметров запроса - this.queries$ = this.route.queryParams.subscribe(queries => { - // Синхронизация текущих значений фильтров с URL - this.currentExperience.set(queries["required_experience"]); - this.currentWorkFormat.set(queries["work_format"]); - this.currentWorkSchedule.set(queries["work_schedule"]); - this.currentSalaryMin.set(queries["salary_min"]); - this.currentSalaryMax.set(queries["salary_max"]); - this.searchValue = queries["role_contains"]; - }); - } - /** Подписка на параметры запроса */ queries$?: Subscription; + /** Состояние открытия фильтра (для мобильной версии) */ filterOpen = signal(false); - /** Форма для фильтрации по зарплате */ - salaryForm: FormGroup; + /** Общее количество элементов */ totalItemsCount = signal(0); @@ -143,6 +105,15 @@ export class VacancyFilterComponent implements OnInit { /** Текущая максимальная зарплата */ currentSalaryMax = signal(undefined); + // Сигналы для значений полей зарплаты + /** Значение поля минимальной зарплаты */ + salaryMinValue = signal(""); + /** Значение поля максимальной зарплаты */ + salaryMaxValue = signal(""); + + // Subject для debounce изменений зарплаты + private salaryChanges$ = new Subject<{ min: string; max: string }>(); + /** Опции фильтра по опыту работы */ readonly filterExperienceOptions = filterExperience; @@ -152,6 +123,61 @@ export class VacancyFilterComponent implements OnInit { /** Опции фильтра по графику работы */ filterWorkScheduleOptions = filterWorkSchedule; + /** + * Инициализация компонента + */ + ngOnInit() { + // Подписка на изменения зарплаты с debounce + this.salaryChanges$.pipe(debounceTime(300)).subscribe(({ min, max }) => { + this.router.navigate([], { + queryParams: { + role_contains: this.searchValue || null, + salary_min: min || null, + salary_max: max || null, + }, + queryParamsHandling: "merge", + relativeTo: this.route, + }); + }); + + // Подписка на изменения параметров запроса + this.queries$ = this.route.queryParams.subscribe(queries => { + // Синхронизация текущих значений фильтров с URL + this.currentExperience.set(queries["required_experience"]); + this.currentWorkFormat.set(queries["work_format"]); + this.currentWorkSchedule.set(queries["work_schedule"]); + this.currentSalaryMin.set(queries["salary_min"]); + this.currentSalaryMax.set(queries["salary_max"]); + this.searchValue = queries["role_contains"]; + + // Синхронизация полей зарплаты + this.salaryMinValue.set(queries["salary_min"] || ""); + this.salaryMaxValue.set(queries["salary_max"] || ""); + }); + } + + /** + * Обработчик изменения минимальной зарплаты + */ + onSalaryMinChange(value: string): void { + this.salaryMinValue.set(value); + this.salaryChanges$.next({ + min: value, + max: this.salaryMaxValue(), + }); + } + + /** + * Обработчик изменения максимальной зарплаты + */ + onSalaryMaxChange(value: string): void { + this.salaryMaxValue.set(value); + this.salaryChanges$.next({ + min: this.salaryMinValue(), + max: value, + }); + } + /** * Установка фильтра по опыту работы * @param event - событие клика @@ -214,25 +240,6 @@ export class VacancyFilterComponent implements OnInit { .then(() => console.debug("Query change from ProjectsComponent")); } - /** - * Применение фильтров - * Обновляет URL с текущими значениями всех фильтров - */ - applyFilter() { - const salaryMin = this.salaryForm.get("salaryMin")?.value || ""; - const salaryMax = this.salaryForm.get("salaryMax")?.value || ""; - - this.router.navigate([], { - queryParams: { - role_contains: this.searchValue || null, - salary_min: salaryMin, - salary_max: salaryMax, - }, - queryParamsHandling: "merge", - relativeTo: this.route, - }); - } - /** * Сброс всех фильтров * Очищает все параметры фильтрации и обновляет URL @@ -241,8 +248,14 @@ export class VacancyFilterComponent implements OnInit { this.currentExperience.set(undefined); this.currentWorkFormat.set(undefined); this.currentWorkSchedule.set(undefined); + this.currentSalaryMax.set(undefined); + this.currentSalaryMin.set(undefined); + + // Сбрасываем значения полей + this.salaryMinValue.set(""); + this.salaryMaxValue.set(""); + this.onSearchValueChanged(""); - this.salaryForm.reset(); this.router .navigate([], { @@ -303,4 +316,9 @@ export class VacancyFilterComponent implements OnInit { map(res => res) ); } + + ngOnDestroy() { + this.queries$?.unsubscribe(); + this.salaryChanges$.complete(); + } } From 22501a9f1cae0a4f5c7a1bbd93a5d12b1a16faec Mon Sep 17 00:00:00 2001 From: Awakich Date: Wed, 27 Aug 2025 10:16:50 +0300 Subject: [PATCH 015/126] change naming of modal to approve skill --- .../src/app/office/profile/detail/main/main.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/social_platform/src/app/office/profile/detail/main/main.component.html b/projects/social_platform/src/app/office/profile/detail/main/main.component.html index abadc1600..f5a9dd5ff 100644 --- a/projects/social_platform/src/app/office/profile/detail/main/main.component.html +++ b/projects/social_platform/src/app/office/profile/detail/main/main.component.html @@ -26,7 +26,7 @@

Обо мне

+ + +
+
+ +

Редактирование изображения перед отправкой!

+
+ + @if (showCropperModalErrorMessage) { +

+ {{ showCropperModalErrorMessage }} +

+ } + +
+ +
+ +
+
Предварительный просмотр:
+ @if (croppedImage) { + Preview + } +
+ +
+ Отменить + Сохранить +
+
+
diff --git a/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.scss b/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.scss index 00cb2eb6f..f857ce42f 100644 --- a/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.scss +++ b/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.scss @@ -1,3 +1,5 @@ +@use "styles/responsive"; + .control { border-radius: 50%; @@ -69,3 +71,142 @@ .placeholder { background-color: var(--gray); } + +.cancel { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 80%; + max-height: calc(100vh - 40px); + padding: 40px 0 80px; + overflow-y: auto; + + @include responsive.apply-desktop { + width: 50%; + } + + &__cross { + position: absolute; + top: 0; + right: 0; + width: 32px; + height: 32px; + cursor: pointer; + + @include responsive.apply-desktop { + top: 8px; + right: 8px; + } + } + + &__cropper-container { + width: 100%; + max-width: 500px; + margin: 20px 0; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + background: var(--background-secondary, #f8f9fa); + + ::ng-deep { + .ngx-ic-main { + border-radius: 8px; + background: white; + } + + .ngx-ic-overlay { + border-radius: 8px; + } + + .ngx-ic-crop { + border-radius: 50%; + box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5); + } + + .ngx-ic-move, .ngx-ic-resize { + background: var(--accent, #007bff); + border: 2px solid white; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + } + + .ngx-ic-crop::before { + border-color: rgba(255, 255, 255, 0.5); + } + } + } + + &__preview { + margin: 20px 0; + text-align: center; + padding: 20px; + background: var(--background-tertiary, #f0f0f0); + border-radius: 12px; + width: 100%; + max-width: 300px; + + &-label { + font-size: 14px; + font-weight: 500; + color: var(--text-secondary, #666); + margin-bottom: 15px; + text-transform: uppercase; + letter-spacing: 0.5px; + } + + &-img { + border: 3px solid var(--border-color, #ddd); + object-fit: cover; + transition: transform 0.2s ease; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + + &:hover { + transform: scale(1.05); + } + + &--round { + border-radius: 50%; + } + } + } + + &__top { + display: flex; + flex-direction: column; + margin-bottom: 10px; + } + + &__title { + text-align: center; + } + + &__text { + text-align: center; + } + + &__buttons { + display: flex; + gap: 10px; + align-items: center; + margin-top: 20px; + + ::ng-deep { + app-button { + .button { + padding-right: 52px; + padding-left: 52px; + } + } + } + } + + &__button { + margin-top: 20px; + } + + &__preview-img { + animation: fadeInScale 0.3s ease-out; + } + + +} diff --git a/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.ts b/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.ts index 474ad3716..b2816f185 100644 --- a/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.ts +++ b/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.ts @@ -5,14 +5,17 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; import { nanoid } from "nanoid"; import { FileService } from "@core/services/file.service"; import { catchError, concatMap, map, of } from "rxjs"; -import { IconComponent } from "@ui/components"; +import { IconComponent, ButtonComponent } from "@ui/components"; import { LoaderComponent } from "../loader/loader.component"; import { CommonModule } from "@angular/common"; +import { ImageCroppedEvent, ImageCropperComponent } from "ngx-image-cropper"; +import { DomSanitizer, SafeUrl } from "@angular/platform-browser"; +import { ModalComponent } from "../modal/modal.component"; /** * Компонент для управления аватаром пользователя. * Реализует ControlValueAccessor для интеграции с Angular Forms. - * Позволяет загружать, обновлять и удалять изображение аватара. + * Позволяет загружать, обрезать, обновлять и удалять изображение аватара. * * Входящие параметры: * - size: размер аватара в пикселях (по умолчанию 140) @@ -34,12 +37,19 @@ import { CommonModule } from "@angular/common"; }, ], standalone: true, - imports: [LoaderComponent, IconComponent, CommonModule], + imports: [ + LoaderComponent, + IconComponent, + CommonModule, + ImageCropperComponent, + ModalComponent, + ButtonComponent, + ], }) export class AvatarControlComponent implements OnInit, ControlValueAccessor { - constructor(private fileService: FileService) {} + constructor(private fileService: FileService, private sanitizer: DomSanitizer) {} - /** Размер авата��а в пикселях */ + /** Размер аватара в пикселях */ @Input() size = 140; /** Состояние ошибки */ @@ -56,6 +66,21 @@ export class AvatarControlComponent implements OnInit, ControlValueAccessor { /** Текущее значение URL изображения */ value = ""; + /** Показывать ли модальное окно кроппера */ + showCropperModal = false; + + /** Текст ошибки при обрезки фотографии */ + showCropperModalErrorMessage = ""; + + /** Исходное изображение для обрезки */ + imageChangedEvent: Event | null = null; + + /** Обрезанное изображение */ + croppedImage: SafeUrl = ""; + + /** Blob обрезанного изображения для загрузки */ + croppedBlob: Blob | null = null; + /** Записывает значение URL изображения */ writeValue(address: string) { this.value = address; @@ -77,17 +102,63 @@ export class AvatarControlComponent implements OnInit, ControlValueAccessor { loading = false; /** - * Обработчик обновления изображения - * Загружает новый файл, при наличии старого - сначала удаляет его + * Обработчик выбора файла - открывает кроппер */ - onUpdate(event: Event) { + onFileSelected(event: Event) { const files = (event.currentTarget as HTMLInputElement).files; if (!files?.length) { return; } + this.imageChangedEvent = event; + this.showCropperModal = true; + } + + /** + * Обработчик обрезки изображения + */ + imageCropped(event: ImageCroppedEvent) { + if (event.objectUrl) { + this.croppedImage = this.sanitizer.bypassSecurityTrustUrl(event.objectUrl); + } + this.croppedBlob = event.blob || null; + } + + /** + * Обработчик загружено фото или нет + */ + imageLoaded() {} + + /** + * Обработчик готовности обрезки фотографии + */ + cropperReady() {} + + /** + * Обработчик ошибки загрузки + */ + loadImageFailed() { + console.error("Не удалось загрузить изображение"); + this.showCropperModalErrorMessage = "Не удалось загрузить изображение. Попробуйте ещё раз!"; + } + + /** + * Сохранить обрезанное изображение + */ + saveCroppedImage() { + if (!this.croppedBlob) { + return; + } + this.loading = true; + this.showCropperModal = false; + + // Создаем файл из blob + const file = new File([this.croppedBlob], "cropped-avatar.jpg", { + type: "image/jpeg", + lastModified: Date.now(), + }); const source = this.value ? this.fileService.deleteFile(this.value).pipe( @@ -95,14 +166,30 @@ export class AvatarControlComponent implements OnInit, ControlValueAccessor { console.error(err); return of({}); }), - concatMap(() => this.fileService.uploadFile(files[0])), + concatMap(() => this.fileService.uploadFile(file)), map(r => r["url"]) ) - : this.fileService.uploadFile(files[0]).pipe(map(r => r.url)); + : this.fileService.uploadFile(file).pipe(map(r => r.url)); source.subscribe(this.updateValue.bind(this)); } + /** + * Закрыть кроппер без сохранения + */ + closeCropper() { + this.showCropperModal = false; + this.imageChangedEvent = null; + this.croppedImage = ""; + this.croppedBlob = null; + + // Сбрасываем значение input + const input = document.getElementById(this.controlId) as HTMLInputElement; + if (input) { + input.value = ""; + } + } + /** * Обновляет значение URL и уведомляет о изменении * @param url - новый URL изображения From ac9046d5b74e95baacc35a2a0b2af00343634861 Mon Sep 17 00:00:00 2001 From: Awakich Date: Thu, 28 Aug 2025 12:48:37 +0300 Subject: [PATCH 017/126] add styles to cropper --- .../avatar-control.component.scss | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.scss b/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.scss index f857ce42f..0ce44e16e 100644 --- a/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.scss +++ b/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.scss @@ -104,15 +104,15 @@ width: 100%; max-width: 500px; margin: 20px 0; - border-radius: 8px; overflow: hidden; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); background: var(--background-secondary, #f8f9fa); + border-radius: 8px; + box-shadow: 0 4px 12px rgb(0 0 0 / 10%); ::ng-deep { .ngx-ic-main { - border-radius: 8px; background: white; + border-radius: 8px; } .ngx-ic-overlay { @@ -121,44 +121,46 @@ .ngx-ic-crop { border-radius: 50%; - box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5); + box-shadow: 0 0 0 9999px rgb(0 0 0 / 50%); } - .ngx-ic-move, .ngx-ic-resize { + .ngx-ic-move, + .ngx-ic-resize { background: var(--accent, #007bff); border: 2px solid white; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + box-shadow: 0 2px 4px rgb(0 0 0 / 20%); } .ngx-ic-crop::before { - border-color: rgba(255, 255, 255, 0.5); + border-color: rgb(255 255 255 / 50%); } } } &__preview { + width: 100%; + max-width: 300px; + padding: 20px; margin: 20px 0; text-align: center; - padding: 20px; background: var(--background-tertiary, #f0f0f0); border-radius: 12px; - width: 100%; - max-width: 300px; &-label { + margin-bottom: 15px; font-size: 14px; font-weight: 500; color: var(--text-secondary, #666); - margin-bottom: 15px; text-transform: uppercase; letter-spacing: 0.5px; } &-img { border: 3px solid var(--border-color, #ddd); - object-fit: cover; + box-shadow: 0 4px 12px rgb(0 0 0 / 10%); transition: transform 0.2s ease; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + object-fit: cover; + animation: fadeInScale 0.3s ease-out; &:hover { transform: scale(1.05); @@ -203,10 +205,4 @@ &__button { margin-top: 20px; } - - &__preview-img { - animation: fadeInScale 0.3s ease-out; - } - - } From 842b9823387df9a93dc6efe34d39c5d9efcf4674 Mon Sep 17 00:00:00 2001 From: Awakich Date: Fri, 29 Aug 2025 11:50:27 +0300 Subject: [PATCH 018/126] fix link to register --- .../src/app/office/program/detail/main/main.component.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.html b/projects/social_platform/src/app/office/program/detail/main/main.component.html index 69b15aed7..75087a60d 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.html +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.html @@ -56,7 +56,11 @@

{{ program.name }}

@if (!program.isUserMember && !registerDateExpired) { - + + + Зарегистрироваться } From 9e34ee3c9d47ba9f6e57438000fdf64ac53e90bc Mon Sep 17 00:00:00 2001 From: Awakich Date: Fri, 29 Aug 2025 11:51:24 +0300 Subject: [PATCH 019/126] add checking to name of program --- .../office/program/detail/main/main.component.html | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.html b/projects/social_platform/src/app/office/program/detail/main/main.component.html index 75087a60d..5e35b0553 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.html +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.html @@ -55,15 +55,16 @@

{{ program.name }}

- @if (!program.isUserMember && !registerDateExpired) { - - + @if (!program.isUserMember && !registerDateExpired) { @if + (program.name.includes("кейс-чемпионат по предпринимательству MIR")) { Зарегистрироваться - } + } @else { + + Зарегистрироваться + + } } From f3edb3b6db66e795faa870e1114f29182d5b6830 Mon Sep 17 00:00:00 2001 From: Awakich Date: Mon, 1 Sep 2025 09:19:00 +0300 Subject: [PATCH 020/126] add check for another program --- .../src/app/office/program/detail/main/main.component.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.html b/projects/social_platform/src/app/office/program/detail/main/main.component.html index 5e35b0553..ce91197e7 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.html +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.html @@ -56,7 +56,8 @@

{{ program.name }}

@if (!program.isUserMember && !registerDateExpired) { @if - (program.name.includes("кейс-чемпионат по предпринимательству MIR")) { + (program.name.includes("кейс-чемпионат по предпринимательству MIR") || + program.name.includes("Кейс-чемпионат MIR")) { Зарегистрироваться From 71af665d405fd440d3208acb424b3aad6300a2a5 Mon Sep 17 00:00:00 2001 From: Awakich Date: Mon, 1 Sep 2025 09:45:31 +0300 Subject: [PATCH 021/126] fix includes check --- .../src/app/office/program/detail/main/main.component.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.html b/projects/social_platform/src/app/office/program/detail/main/main.component.html index ce91197e7..8dc25671b 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.html +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.html @@ -56,8 +56,7 @@

{{ program.name }}

@if (!program.isUserMember && !registerDateExpired) { @if - (program.name.includes("кейс-чемпионат по предпринимательству MIR") || - program.name.includes("Кейс-чемпионат MIR")) { + (program.name.includes("Кейс-чемпионат MIR")) { Зарегистрироваться From a94d1bb1a8209e5feec7c033e4832fdc055784cd Mon Sep 17 00:00:00 2001 From: Awakich Date: Mon, 1 Sep 2025 14:06:37 +0300 Subject: [PATCH 022/126] fix cropper & add modal to permission to projects in program --- .../app/auth/register/register.component.ts | 4 +- .../set-password/set-password.component.html | 99 +++++++++++++++---- .../set-password/set-password.component.ts | 4 +- .../program/detail/main/main.component.html | 19 ++++ .../program/detail/main/main.component.scss | 49 +++++++++ .../program/detail/main/main.component.ts | 24 +++++ .../detail/projects/projects.component.scss | 1 + .../detail/projects/projects.component.ts | 18 ++-- .../detail/projects/projects.resolver.ts | 31 ++++-- .../program/services/program.service.ts | 4 +- .../autocomplete-input.component.scss | 2 +- .../avatar-control.component.html | 14 --- .../avatar-control.component.scss | 36 ------- 13 files changed, 217 insertions(+), 88 deletions(-) diff --git a/projects/social_platform/src/app/auth/register/register.component.ts b/projects/social_platform/src/app/auth/register/register.component.ts index ac8e3f88b..1aff07cc9 100644 --- a/projects/social_platform/src/app/auth/register/register.component.ts +++ b/projects/social_platform/src/app/auth/register/register.component.ts @@ -73,10 +73,10 @@ export class RegisterComponent implements OnInit { "", [Validators.required, Validators.email, this.validationService.useEmailValidator()], ], - password: ["", [Validators.required, this.validationService.usePasswordValidator(6)]], + password: ["", [Validators.required, this.validationService.usePasswordValidator(8)]], repeatedPassword: ["", [Validators.required]], }, - { validators: [this.validationService.useMatchValidator("password", "repeatedPassword")] } + { validators: [validationService.useMatchValidator("password", "repeatedPassword")] } ); } diff --git a/projects/social_platform/src/app/auth/set-password/set-password.component.html b/projects/social_platform/src/app/auth/set-password/set-password.component.html index 0971a9d17..2b1374465 100644 --- a/projects/social_platform/src/app/auth/set-password/set-password.component.html +++ b/projects/social_platform/src/app/auth/set-password/set-password.component.html @@ -38,17 +38,47 @@

Новый пароль

> } - @if (password | controlError: "required") { -
- {{ errorMessage.VALIDATION_REQUIRED }} -
- } @if (password | controlError: "minlength") { -
- @if (password.errors) { - {{ errorMessage.VALIDATION_TOO_SHORT }} - {{ password.errors["minlength"]["requiredLength"] }} + @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 }} +
} -
+ } } @if (passwordForm.get("passwordRepeated"); as passwordRepeated) { @@ -61,14 +91,47 @@

Новый пароль

formControlName="passwordRepeated" placeholder="Пароль" > - @if (passwordRepeated | controlError: "required") { -
- {{ errorMessage.VALIDATION_REQUIRED }} -
- } @if (passwordForm | controlError: "unMatch") { -
- {{ errorMessage.VALIDATION_PASSWORD_UNMATCH }} -
+ @if (credsSubmitInitiated) { + + @if (passwordRepeated | controlError: "required") { +
+ {{ errorMessage.VALIDATION_REQUIRED }} +
+ } @if (passwordRepeated | controlError: "passwordTooShort") { +
+ @if (passwordRepeated.errors) { Пароль должен содержать минимум + {{ passwordRepeated.errors["passwordTooShort"]["requiredLength"] }} символов } +
+ } @if (passwordRepeated | controlError: "passwordNoUppercase") { +
+ Пароль должен содержать минимум одну заглавную букву (A-Z) +
+ } @if (passwordRepeated | controlError: "passwordNoLowercase") { +
+ Пароль должен содержать минимум одну строчную букву (a-z) +
+ } @if (passwordRepeated | controlError: "passwordNoNumber") { +
Пароль должен содержать минимум одну цифру (0-9)
+ } @if (passwordRepeated | controlError: "passwordNoSpecialChar") { +
+ Пароль должен содержать минимум один специальный символ +
+ } @if (passwordRepeated | controlError: "passwordHasSpaces") { +
Пароль не должен содержать пробелы
+ } @if (passwordRepeated | controlError: "passwordHasSequence") { +
+ Пароль не должен содержать последовательности символов (123456, abcdef и т.д.) +
+ } @if (passwordRepeated | controlError: "passwordHasRepeating") { +
+ Пароль не должен содержать более 2 одинаковых символов подряд +
+ } @if (passwordRepeated | controlError: "unMatch") { +
+ {{ errorMessage.VALIDATION_PASSWORD_UNMATCH }} +
+ } +
} } @if (errorRequest) { diff --git a/projects/social_platform/src/app/auth/set-password/set-password.component.ts b/projects/social_platform/src/app/auth/set-password/set-password.component.ts index fa9399438..315f86eb1 100644 --- a/projects/social_platform/src/app/auth/set-password/set-password.component.ts +++ b/projects/social_platform/src/app/auth/set-password/set-password.component.ts @@ -41,7 +41,7 @@ export class SetPasswordComponent implements OnInit { ) { this.passwordForm = this.fb.group( { - password: ["", [Validators.required, Validators.minLength(8)]], + password: ["", [Validators.required, this.validationService.usePasswordValidator(8)]], passwordRepeated: ["", [Validators.required]], }, { validators: [validationService.useMatchValidator("password", "passwordRepeated")] } @@ -52,6 +52,7 @@ export class SetPasswordComponent implements OnInit { isSubmitting = false; errorMessage = ErrorMessage; errorRequest = false; + credsSubmitInitiated = false; showPassword = false; @@ -68,6 +69,7 @@ export class SetPasswordComponent implements OnInit { } onSubmit() { + this.credsSubmitInitiated = true; const token = this.route.snapshot.queryParamMap.get("token"); if (!token || !this.validationService.getFormValidation(this.passwordForm)) return; diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.html b/projects/social_platform/src/app/office/program/detail/main/main.component.html index 8dc25671b..6d542b082 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.html +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.html @@ -168,4 +168,23 @@ + + +
+
+ +

Ошибка в доступе к программе!

+
+ + @if (showProgramModalErrorMessage()) { +

+ {{ showProgramModalErrorMessage() }} +

+ } + + Хорошо +
+
} diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.scss b/projects/social_platform/src/app/office/program/detail/main/main.component.scss index bc9c3c020..97f3c48c2 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.scss +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.scss @@ -397,3 +397,52 @@ margin-top: 20px; } } + + +.cancel { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 80%; + max-height: calc(100vh - 40px); + padding: 40px 0 80px; + overflow-y: auto; + + @include responsive.apply-desktop { + width: 50%; + } + + &__cross { + position: absolute; + top: 0; + right: 0; + width: 32px; + height: 32px; + cursor: pointer; + + @include responsive.apply-desktop { + top: 8px; + right: 8px; + } + } + + &__top { + display: flex; + flex-direction: column; + margin-bottom: 10px; + } + + &__title { + text-align: center; + } + + &__text { + text-align: center; + } + + &__button { + margin-top: 20px; + } + +} diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.ts b/projects/social_platform/src/app/office/program/detail/main/main.component.ts index d20b835ed..afb90f219 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.ts +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.ts @@ -25,6 +25,7 @@ import { ApiPagination } from "@models/api-pagination.model"; import { TagComponent } from "@ui/components/tag/tag.component"; import { NewsFormComponent } from "@office/shared/news-form/news-form.component"; import { ProjectService } from "@office/services/project.service"; +import { ModalComponent } from "@ui/components/modal/modal.component"; /** * Главный компонент детальной страницы программы @@ -86,6 +87,7 @@ import { ProjectService } from "@office/services/project.service"; ParseBreaksPipe, ParseLinksPipe, NewsFormComponent, + ModalComponent, ], }) export class ProgramDetailMainComponent implements OnInit, OnDestroy { @@ -103,6 +105,9 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { fetchLimit = signal(10); fetchPage = signal(0); + showProgramModal = signal(false); + showProgramModalErrorMessage = signal(null); + programId?: number; subscriptions$ = signal([]); @@ -118,6 +123,20 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { ) .subscribe(); + const routeModalSub$ = this.route.queryParams.subscribe(param => { + if (param["access"] === "accessDenied") { + this.showProgramModal.set(true); + this.showProgramModalErrorMessage.set("У вас не доступа к этой вкладке!"); + + this.router.navigate([], { + relativeTo: this.route, + queryParams: { access: null }, + queryParamsHandling: "merge", + replaceUrl: true, + }); + } + }); + const program$ = this.route.data .pipe( map(r => r["data"]), @@ -142,6 +161,7 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { this.subscriptions$().push(program$); this.subscriptions$().push(programIdSubscription$); + this.subscriptions$().push(routeModalSub$); } ngAfterViewInit() { @@ -268,6 +288,10 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { }); } + closeModal(): void { + this.showProgramModal.set(false); + } + program?: Program; registerDateExpired!: boolean; descriptionExpandable!: boolean; diff --git a/projects/social_platform/src/app/office/program/detail/projects/projects.component.scss b/projects/social_platform/src/app/office/program/detail/projects/projects.component.scss index fef6abb23..97ad6ef38 100644 --- a/projects/social_platform/src/app/office/program/detail/projects/projects.component.scss +++ b/projects/social_platform/src/app/office/program/detail/projects/projects.component.scss @@ -37,6 +37,7 @@ grid-template-columns: repeat(2, 1fr); gap: 20px 40px; width: 70%; + height: 100%; } } diff --git a/projects/social_platform/src/app/office/program/detail/projects/projects.component.ts b/projects/social_platform/src/app/office/program/detail/projects/projects.component.ts index 98a04bedd..03a812d96 100644 --- a/projects/social_platform/src/app/office/program/detail/projects/projects.component.ts +++ b/projects/social_platform/src/app/office/program/detail/projects/projects.component.ts @@ -132,9 +132,11 @@ export class ProgramProjectsComponent implements OnInit, AfterViewInit, OnDestro tap(r => (this.projectsTotalCount = r["count"])), map(r => r["results"]) ) - .subscribe(projects => { - this.projects = projects; - this.searchedProjects = projects; + .subscribe({ + next: projects => { + this.projects = projects; + this.searchedProjects = projects; + }, }); const searchFormSearch$ = this.searchForm.get("search")?.valueChanges.subscribe(search => { @@ -171,21 +173,21 @@ export class ProgramProjectsComponent implements OnInit, AfterViewInit, OnDestro const hasFilters = reqQuery && reqQuery["filters"] && Object.keys(reqQuery["filters"]).length > 0; - const params = new HttpParams({ fromObject: { partner_program: programId } }); + const params = new HttpParams({ fromObject: { offset: 0, limit: 21 } }); if (hasFilters) { return this.programService.createProgramFilters(programId, reqQuery["filters"]).pipe( catchError(err => { console.error("createFilters failed, fallback to getAllProjects()", err); - return this.programService.getAllProjects(params); + return this.programService.getAllProjects(programId, params); }) ); } - return this.programService.getAllProjects(params).pipe( + return this.programService.getAllProjects(programId, params).pipe( catchError(err => { console.error("getAllProjects failed", err); - return this.programService.getAllProjects(params); + return this.programService.getAllProjects(programId, params); }) ); } @@ -298,7 +300,7 @@ export class ProgramProjectsComponent implements OnInit, AfterViewInit, OnDestro const limit = this.perPage; return this.programService - .getAllProjects(new HttpParams({ fromObject: { partner_program: programId, offset, limit } })) + .getAllProjects(programId, new HttpParams({ fromObject: { offset, limit } })) .pipe( tap(projects => { this.projectsTotalCount = projects.count; diff --git a/projects/social_platform/src/app/office/program/detail/projects/projects.resolver.ts b/projects/social_platform/src/app/office/program/detail/projects/projects.resolver.ts index 8724b79bb..c474a63ce 100644 --- a/projects/social_platform/src/app/office/program/detail/projects/projects.resolver.ts +++ b/projects/social_platform/src/app/office/program/detail/projects/projects.resolver.ts @@ -1,11 +1,12 @@ /** @format */ import { inject } from "@angular/core"; -import { ActivatedRouteSnapshot, ResolveFn } from "@angular/router"; +import { ActivatedRoute, ActivatedRouteSnapshot, ResolveFn, Router } from "@angular/router"; import { ProgramService } from "@office/program/services/program.service"; import { Project } from "@models/project.model"; import { ApiPagination } from "@models/api-pagination.model"; import { HttpParams } from "@angular/common/http"; +import { catchError, EMPTY } from "rxjs"; /** * Резолвер для предзагрузки проектов программы @@ -33,9 +34,27 @@ export const ProgramProjectsResolver: ResolveFn> = ( route: ActivatedRouteSnapshot ) => { const programService = inject(ProgramService); - return programService.getAllProjects( - new HttpParams({ - fromObject: { partner_program: route.parent?.params["programId"], offset: 0, limit: 21 }, - }) - ); + const programId = route.parent?.params["programId"]; + const router = inject(Router); + + return programService + .getAllProjects( + programId, + new HttpParams({ + fromObject: { offset: 0, limit: 21 }, + }) + ) + .pipe( + catchError(error => { + if (error.status === 403) { + router.navigate([], { + queryParams: { access: "accessDenied" }, + queryParamsHandling: "merge", + replaceUrl: true, + }); + } + + return EMPTY; + }) + ); }; 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 f372baa8f..397fcd89b 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 @@ -79,8 +79,8 @@ export class ProgramService { return this.apiService.post(`${this.PROGRAMS_URL}/${programId}/register/`, additionalData); } - getAllProjects(params?: HttpParams): Observable> { - return this.apiService.get(`${this.PROJECTS_URL}/`, params); + getAllProjects(programId: number, params?: HttpParams): Observable> { + return this.apiService.get(`${this.PROGRAMS_URL}/${programId}/projects`, params); } getAllMembers(programId: number, skip: number, take: number): Observable> { diff --git a/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.scss b/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.scss index 470b7f64a..3d533c241 100644 --- a/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.scss +++ b/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.scss @@ -134,7 +134,7 @@ position: absolute; top: 50%; right: 10px; - z-index: 4; + z-index: 2; display: flex; align-items: center; transform: translateY(-50%); diff --git a/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.html b/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.html index 033d4f4e1..14bd2607e 100644 --- a/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.html +++ b/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.html @@ -73,20 +73,6 @@ > -
-
Предварительный просмотр:
- @if (croppedImage) { - Preview - } -
-
Date: Mon, 1 Sep 2025 14:07:04 +0300 Subject: [PATCH 023/126] add modal styles --- .../src/app/office/program/detail/main/main.component.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.scss b/projects/social_platform/src/app/office/program/detail/main/main.component.scss index 97f3c48c2..be57ed88c 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.scss +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.scss @@ -398,7 +398,6 @@ } } - .cancel { display: flex; flex-direction: column; @@ -444,5 +443,4 @@ &__button { margin-top: 20px; } - } From 6b4cde4c0c0cfad7e0869b00f35f320a2c35d79d Mon Sep 17 00:00:00 2001 From: Awakich Date: Mon, 1 Sep 2025 14:23:41 +0300 Subject: [PATCH 024/126] run format --- .../office/program/detail/main/main.component.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.ts b/projects/social_platform/src/app/office/program/detail/main/main.component.ts index afb90f219..8b5ae0ea4 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.ts +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.ts @@ -1,5 +1,4 @@ /** @format */ - import { ChangeDetectorRef, Component, @@ -111,7 +110,6 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { programId?: number; subscriptions$ = signal([]); - ngOnInit(): void { const programIdSubscription$ = this.route.params .pipe( @@ -167,9 +165,7 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { ngAfterViewInit() { const descElement = this.descEl?.nativeElement; this.descriptionExpandable = descElement?.clientHeight < descElement?.scrollHeight; - this.cdRef.detectChanges(); - const target = document.querySelector(".office__body"); if (target) { const scrollEvents$ = fromEvent(target, "scroll") @@ -178,7 +174,6 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { throttleTime(2000) ) .subscribe(); - this.subscriptions$().push(scrollEvents$); } } @@ -203,19 +198,14 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { const target = document.querySelector(".office__body"); if (!target) return of({}); - const scrollBottom = target.scrollHeight - target.scrollTop - target.clientHeight; - if (scrollBottom > 0) return of({}); - this.fetchPage.update(p => p + 1); - return this.fetchNews(this.fetchPage() * this.fetchLimit(), this.fetchLimit()); } fetchNews(offset: number, limit: number) { const programId = this.route.snapshot.params["programId"]; - return this.programNewsService.fetchNews(limit, offset, programId).pipe( tap(({ count, results }) => { this.totalNewsCount.set(count); @@ -226,14 +216,12 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { @ViewChild(NewsFormComponent) newsFormComponent?: NewsFormComponent; @ViewChild("descEl") descEl?: ElementRef; - onNewsInVew(entries: IntersectionObserverEntry[]): void { const ids = entries.map(e => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore return e.target.dataset.id; }); - this.programNewsService.readNews(this.route.snapshot.params["programId"], ids).subscribe(noop); } @@ -249,7 +237,6 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { onDelete(newsId: number) { const item = this.news().find((n: any) => n.id === newsId); if (!item) return; - this.programNewsService.deleteNews(this.route.snapshot.params["programId"], newsId).subscribe({ next: () => { const index = this.news().findIndex(news => news.id === newsId); @@ -261,7 +248,6 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { onLike(newsId: number) { const item = this.news().find((n: any) => n.id === newsId); if (!item) return; - this.programNewsService .toggleLike(this.route.snapshot.params["programId"], newsId, !item.isUserLiked) .subscribe(() => { @@ -281,7 +267,6 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { ...this.projectService.projectsCount.getValue(), my: this.projectService.projectsCount.getValue().my + 1, }); - this.router .navigateByUrl(`/office/projects/${project.id}/edit?editingStep=main`) .then(() => console.debug("Route change from ProjectsComponent")); From 99434cc7090a3b0321b6d39eb5342cb18207ea15 Mon Sep 17 00:00:00 2001 From: Awakich Date: Tue, 2 Sep 2025 09:54:46 +0300 Subject: [PATCH 025/126] add loading service to project & fix program loading error --- .../social_platform/src/app/app.component.ts | 24 +++- .../program/detail/main/main.component.ts | 125 +++++++++--------- .../app/office/services/loading.service.ts | 27 ++++ 3 files changed, 107 insertions(+), 69 deletions(-) create mode 100644 projects/social_platform/src/app/office/services/loading.service.ts diff --git a/projects/social_platform/src/app/app.component.ts b/projects/social_platform/src/app/app.component.ts index be9cf814e..861f9e768 100644 --- a/projects/social_platform/src/app/app.component.ts +++ b/projects/social_platform/src/app/app.component.ts @@ -18,6 +18,7 @@ import { import { MatProgressBarModule } from "@angular/material/progress-bar"; import { AsyncPipe, NgIf } from "@angular/common"; import { TokenService } from "@corelib"; +import { LoadingService } from "@office/services/loading.service"; /** * Корневой компонент приложения @@ -36,7 +37,8 @@ export class AppComponent implements OnInit, OnDestroy { constructor( private authService: AuthService, private tokenService: TokenService, - private router: Router + private router: Router, + private loadingService: LoadingService ) {} ngOnInit(): void { @@ -45,17 +47,26 @@ export class AppComponent implements OnInit, OnDestroy { this.authService.getChangeableRoles(), ]).subscribe(noop); - this.showLoaderEvents = this.router.events.pipe( + const showLoaderEvents = this.router.events.pipe( filter(evt => evt instanceof ResolveStart), map(() => true) ); - this.hideLoaderEvents = this.router.events.pipe( + + const hideLoaderEvents = this.router.events.pipe( filter(evt => evt instanceof ResolveEnd), debounceTime(200), map(() => false) ); - this.isLoading$ = merge(this.hideLoaderEvents, this.showLoaderEvents); + this.routerLoadingSub$ = merge(hideLoaderEvents, showLoaderEvents).subscribe(isLoading => { + if (isLoading) { + this.loadingService.show(); + } else { + this.loadingService.hide(); + } + }); + + this.isLoading$ = this.loadingService.isLoading$; if (location.pathname === "/") { if (this.tokenService.getTokens() === null) { @@ -80,10 +91,12 @@ export class AppComponent implements OnInit, OnDestroy { ngOnDestroy(): void { this.rolesSub$?.unsubscribe(); + this.routerLoadingSub$?.unsubscribe(); this.appHeight$?.unsubscribe(); } rolesSub$?: Subscription; + routerLoadingSub$?: Subscription; private loadEvent?: Observable; private resizeEvent?: Observable; @@ -91,7 +104,4 @@ export class AppComponent implements OnInit, OnDestroy { private appHeight$?: Subscription; isLoading$?: Observable; - private showLoaderEvents?: Observable; - - private hideLoaderEvents?: Observable; } diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.ts b/projects/social_platform/src/app/office/program/detail/main/main.component.ts index 8b5ae0ea4..60f92f0fc 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.ts +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.ts @@ -10,7 +10,17 @@ import { } from "@angular/core"; import { ProgramService } from "@office/program/services/program.service"; import { ActivatedRoute, Router, RouterLink } from "@angular/router"; -import { concatMap, fromEvent, map, noop, of, Subscription, tap, throttleTime } from "rxjs"; +import { + concatMap, + fromEvent, + map, + noop, + Observable, + of, + Subscription, + tap, + throttleTime, +} from "rxjs"; import { Program } from "@office/program/models/program.model"; import { ProgramNewsService } from "@office/program/services/program-news.service"; import { FeedNews } from "@office/projects/models/project-news.model"; @@ -25,51 +35,10 @@ import { TagComponent } from "@ui/components/tag/tag.component"; import { NewsFormComponent } from "@office/shared/news-form/news-form.component"; import { ProjectService } from "@office/services/project.service"; import { ModalComponent } from "@ui/components/modal/modal.component"; +import { MatProgressBarModule } from "@angular/material/progress-bar"; +import { AsyncPipe } from "@angular/common"; +import { LoadingService } from "@office/services/loading.service"; -/** - * Главный компонент детальной страницы программы - * - * Отображает основную информацию о программе и новостную ленту: - * - Детальное описание программы с возможностью развернуть/свернуть - * - Информацию о датах и регистрации - * - Новостную ленту для участников программы - * - Форму добавления новостей - * - Взаимодействие с новостями (лайки, просмотры) - * - * Принимает: - * @param {ProgramService} programService - Сервис программ - * @param {ProgramNewsService} programNewsService - Сервис новостей программы - * @param {ActivatedRoute} route - Для получения данных программы - * @param {ChangeDetectorRef} cdRef - Для ручного обновления представления - * - * Состояние (signals): - * @property {Signal} news - Массив новостей программы - * @property {Signal} totalNewsCount - Общее количество новостей - * @property {Signal} fetchLimit - Лимит загрузки новостей (10) - * @property {Signal} fetchPage - Текущая страница новостей - * @property {Signal} subscriptions$ - Подписки для очистки - * - * Данные программы: - * @property {Program} program - Объект программы - * @property {boolean} registerDateExpired - Истек ли срок регистрации - * @property {boolean} descriptionExpandable - Можно ли развернуть описание - * @property {boolean} readFullDescription - Развернуто ли описание - * - * ViewChild: - * @ViewChild NewsFormComponent - Ссылка на компонент формы новостей - * @ViewChild descEl - Ссылка на элемент описания - * - * Методы: - * @method fetchNews(offset, limit) - Загружает новости с пагинацией - * @method onScroll() - Обработчик прокрутки для подгрузки новостей - * @method onNewsInVew(entries) - Отмечает новости как просмотренные - * @method onAddNews(news) - Добавляет новую новость - * @method onLike(newsId) - Переключает лайк новости - * @method onExpandDescription() - Разворачивает/сворачивает описание - * - * Возвращает: - * HTML шаблон с информацией о программе и новостной лентой - */ @Component({ selector: "app-main", templateUrl: "./main.component.html", @@ -83,10 +52,12 @@ import { ModalComponent } from "@ui/components/modal/modal.component"; ProgramNewsCardComponent, TagComponent, UserLinksPipe, + AsyncPipe, ParseBreaksPipe, ParseLinksPipe, NewsFormComponent, ModalComponent, + MatProgressBarModule, ], }) export class ProgramDetailMainComponent implements OnInit, OnDestroy { @@ -96,7 +67,8 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { private readonly projectService: ProjectService, private readonly router: Router, private readonly route: ActivatedRoute, - private readonly cdRef: ChangeDetectorRef + private readonly cdRef: ChangeDetectorRef, + private readonly loadingService: LoadingService ) {} news = signal([]); @@ -110,6 +82,7 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { programId?: number; subscriptions$ = signal([]); + ngOnInit(): void { const programIdSubscription$ = this.route.params .pipe( @@ -123,6 +96,8 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { const routeModalSub$ = this.route.queryParams.subscribe(param => { if (param["access"] === "accessDenied") { + this.loadingService.hide(); + this.showProgramModal.set(true); this.showProgramModalErrorMessage.set("У вас не доступа к этой вкладке!"); @@ -150,13 +125,25 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { } }) ) - .subscribe(news => { - if (news.results?.length) { - this.news.set(news.results); - this.totalNewsCount.set(news.count); - } + .subscribe({ + next: news => { + if (news.results?.length) { + this.news.set(news.results); + this.totalNewsCount.set(news.count); + } + + this.loadingService.hide(); + }, + error: () => { + this.loadingService.hide(); + + this.showProgramModal.set(true); + this.showProgramModalErrorMessage.set("Произошла ошибка при загрузке программы"); + }, }); + this.loadEvent = fromEvent(window, "load"); + this.subscriptions$().push(program$); this.subscriptions$().push(programIdSubscription$); this.subscriptions$().push(routeModalSub$); @@ -216,6 +203,7 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { @ViewChild(NewsFormComponent) newsFormComponent?: NewsFormComponent; @ViewChild("descEl") descEl?: ElementRef; + onNewsInVew(entries: IntersectionObserverEntry[]): void { const ids = entries.map(e => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -261,21 +249,34 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { this.readFullDescription = !isExpanded; } + closeModal(): void { + this.showProgramModal.set(false); + this.loadingService.hide(); + } + addProject(): void { - this.projectService.create().subscribe(project => { - this.projectService.projectsCount.next({ - ...this.projectService.projectsCount.getValue(), - my: this.projectService.projectsCount.getValue().my + 1, - }); - this.router - .navigateByUrl(`/office/projects/${project.id}/edit?editingStep=main`) - .then(() => console.debug("Route change from ProjectsComponent")); + this.loadingService.show(); + + this.projectService.create().subscribe({ + next: project => { + this.projectService.projectsCount.next({ + ...this.projectService.projectsCount.getValue(), + my: this.projectService.projectsCount.getValue().my + 1, + }); + this.router + .navigateByUrl(`/office/projects/${project.id}/edit?editingStep=main`) + .then(() => { + console.debug("Route change from ProjectsComponent"); + }); + }, + error: error => { + this.loadingService.hide(); + console.error("Project creation error:", error); + }, }); } - closeModal(): void { - this.showProgramModal.set(false); - } + private loadEvent?: Observable; program?: Program; registerDateExpired!: boolean; diff --git a/projects/social_platform/src/app/office/services/loading.service.ts b/projects/social_platform/src/app/office/services/loading.service.ts new file mode 100644 index 000000000..efafda4d5 --- /dev/null +++ b/projects/social_platform/src/app/office/services/loading.service.ts @@ -0,0 +1,27 @@ +/** @format */ + +import { Injectable } from "@angular/core"; +import { BehaviorSubject, Observable } from "rxjs"; + +@Injectable({ + providedIn: "root", +}) +export class LoadingService { + private _isLoading$ = new BehaviorSubject(false); + + get isLoading$(): Observable { + return this._isLoading$.asObservable(); + } + + show(): void { + this._isLoading$.next(true); + } + + hide(): void { + this._isLoading$.next(false); + } + + toggle(): void { + this._isLoading$.next(!this._isLoading$.value); + } +} From 18402a539a818390dd0c009a2c9d5ead5b54241a Mon Sep 17 00:00:00 2001 From: Awakich Date: Tue, 2 Sep 2025 16:14:31 +0300 Subject: [PATCH 026/126] change naming in modal of program & change new project card design --- .../feed/shared/new-project/new-project.component.html | 8 ++++---- .../feed/shared/new-project/new-project.component.scss | 2 +- .../app/office/program/detail/main/main.component.html | 4 +++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.html b/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.html index a2734f7f6..3f0595d5a 100644 --- a/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.html +++ b/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.html @@ -2,7 +2,7 @@

- Добро пожаловать + Добро пожаловать на платформу! {{ feedItem.name }}

[url]="feedItem.imageAddress" [hasBorder]="true" > -

+ Перейти в проектПосмотреть проект

diff --git a/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.scss b/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.scss index 88f4f5688..65ea011f9 100644 --- a/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.scss +++ b/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.scss @@ -30,7 +30,7 @@ } &__avatar { - margin: 5px 0; + margin: 15px 0; } &__text { diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.html b/projects/social_platform/src/app/office/program/detail/main/main.component.html index 6d542b082..ab6dbd15b 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.html +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.html @@ -173,7 +173,9 @@
-

Ошибка в доступе к программе!

+

+ Вы не являетесь экспертом или организатором программы! +

@if (showProgramModalErrorMessage()) { From 58daf770a1431d40309454ff5f4725e2cc485d61 Mon Sep 17 00:00:00 2001 From: Awakich Date: Thu, 4 Sep 2025 16:30:44 +0300 Subject: [PATCH 027/126] change design of achievements, additional, team on half & remove old params in project --- projects/core/src/consts/navProjectItems.ts | 10 +- .../office/projects/edit/edit.component.html | 57 +++++----- .../office/projects/edit/edit.component.scss | 31 ++---- .../services/project-achievements.service.ts | 18 +-- .../edit/services/project-form.service.ts | 20 +--- .../project-achievement-step.component.html | 104 ++++++++---------- .../project-achievement-step.component.scss | 54 ++++----- .../project-achievement-step.component.ts | 53 ++++++++- .../project-additional-step.component.html | 15 ++- .../project-additional-step.component.scss | 14 +++ .../project-additional-step.component.ts | 12 +- .../project-main-step.component.html | 33 ++---- .../project-main-step.component.ts | 6 +- .../project-navigation.component.html | 8 +- .../project-navigation.component.scss | 10 +- .../project-navigation.component.ts | 3 + .../project-team-step.component.html | 21 ++-- .../project-team-step.component.scss | 14 ++- .../collaborator-card.component.html | 19 +--- .../collaborator-card.component.scss | 18 ++- .../collaborator-card.component.ts | 3 +- .../invite-card/invite-card.component.html | 28 +++-- .../invite-card/invite-card.component.scss | 11 +- .../invite-card/invite-card.component.ts | 2 + .../components/button/button.component.html | 2 +- .../components/button/button.component.scss | 3 +- .../ui/components/input/input.component.scss | 2 +- .../ui/components/input/input.component.ts | 2 +- .../components/select/select.component.scss | 2 +- .../src/app/utils/generate-trl-list.ts | 38 +++++++ .../src/assets/icons/svg/achievements.svg | 3 + .../src/assets/icons/svg/additional.svg | 3 + .../src/assets/icons/svg/contacts.svg | 3 + .../src/assets/icons/svg/error.svg | 4 +- .../src/assets/icons/svg/main.svg | 3 + .../src/assets/icons/svg/sad-smile.svg | 3 + .../src/assets/icons/svg/team.svg | 3 + .../src/assets/icons/svg/vacancies.svg | 3 + .../assets/icons/symbol/svg/sprite.css.svg | 2 +- .../src/styles/_typography.scss | 46 +++++++- 40 files changed, 407 insertions(+), 279 deletions(-) create mode 100644 projects/social_platform/src/app/utils/generate-trl-list.ts create mode 100644 projects/social_platform/src/assets/icons/svg/achievements.svg create mode 100644 projects/social_platform/src/assets/icons/svg/additional.svg create mode 100644 projects/social_platform/src/assets/icons/svg/contacts.svg create mode 100644 projects/social_platform/src/assets/icons/svg/main.svg create mode 100644 projects/social_platform/src/assets/icons/svg/sad-smile.svg create mode 100644 projects/social_platform/src/assets/icons/svg/team.svg create mode 100644 projects/social_platform/src/assets/icons/svg/vacancies.svg diff --git a/projects/core/src/consts/navProjectItems.ts b/projects/core/src/consts/navProjectItems.ts index f5b7cf52f..2250f566b 100644 --- a/projects/core/src/consts/navProjectItems.ts +++ b/projects/core/src/consts/navProjectItems.ts @@ -9,32 +9,26 @@ import { EditStep } from "@office/projects/edit/services/project-step.service"; export const navItems = [ { step: "main" as EditStep, // Идентификатор шага - src: "/assets/images/projects/edit/main.svg", // Путь к иконке label: "Основные данные", // Отображаемый текст }, { step: "contacts" as EditStep, - src: "/assets/images/projects/edit/contacts.svg", - label: "Контакты и ссылки", + label: "Партнеры и ресурсы", }, { step: "achievements" as EditStep, - src: "/assets/images/projects/edit/achievements.svg", label: "Достижения", }, { step: "vacancies" as EditStep, - src: "/assets/images/projects/edit/vacancies.svg", label: "Вакансии", }, { step: "team" as EditStep, - src: "/assets/images/projects/edit/team.svg", label: "Команда", }, { step: "additional" as EditStep, - src: "/assets/images/projects/edit/additional.svg", - label: "Доп. сведения", + label: "Данные для конкурсов", }, ]; diff --git a/projects/social_platform/src/app/office/projects/edit/edit.component.html b/projects/social_platform/src/app/office/projects/edit/edit.component.html index 000f6831d..8056f8e85 100644 --- a/projects/social_platform/src/app/office/projects/edit/edit.component.html +++ b/projects/social_platform/src/app/office/projects/edit/edit.component.html @@ -1,12 +1,37 @@
-

Редактировать проект

- - Назад + +

Редактировать проект

+
+
+ + Удалить проект + + - + Сохранить черновик + + + {{ isCompetitive ? "Отправить заявку" : "Сохранить" }} + +
@@ -53,28 +78,6 @@ > }
-
- - Удалить проект - - - Сохранить черновик - - - {{ isCompetitive ? "Отправить заявку" : "Сохранить" }} - -
{ const achievementGroup = this.fb.group({ id: achievement.id ?? index, - title: achievement.title || "", - status: achievement.status || "", + achievementsName: [achievement.achievementsName || "", Validators.required], + achievementsDate: [achievement.achievementsDate || "", Validators.required], }); achievementsFormArray.push(achievementGroup); }); @@ -207,10 +203,6 @@ export class ProjectFormService { return this.projectForm.get("industryId"); } - public get step() { - return this.projectForm.get("step"); - } - public get description() { return this.projectForm.get("description"); } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.html b/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.html index 14d73795b..1fbee5b94 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.html +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.html @@ -2,70 +2,56 @@
-
- @if(achievementsName; as achievementsName){ -
- - - @if ( !!( (achievementsName | controlError: "required") && - projectForm.get('achievementsName')?.touched && projSubmitInitiated ) ) { -
- {{ errorMessage.VALIDATION_REQUIRED }} -
- } -
- } -
- @if (achievementsPrize; as achievementsPrize) { -
- - @if ( !!( (achievementsPrize | controlError: "required") && achievementsPrize?.touched && - projSubmitInitiated ) ) { -
- {{ errorMessage.VALIDATION_REQUIRED }} -
+
    + @for (control of achievements.controls; track control.value.id; let i = $index) { +
  • +
    + @if (achievements.at(i)?.get("achievementsName"); as achievementsName) { +
    + + + @if (achievementsName | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } @if (achievements.at(i).get("achievementsDate"); as achievementsDate) { +
    + + + @if (achievementsDate | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } + + + +
    +
  • } -
- } +
+
- - Добавить достижение + + Добавить достижение -
    - @if(achievementsItems().length || achievements.length){ @for (achievementItem of - achievements.value; track $index) { -
  • - -
  • - } } -
+ @if (!achievements.length) { + + }
diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.scss index 2226fd28b..c8aa65544 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.scss @@ -4,34 +4,28 @@ @use "styles/typography"; .project { + position: relative; + &__inner { width: 100%; margin-bottom: 25px; @include responsive.apply-desktop { display: flex; - gap: 90px; + flex-direction: column; justify-content: space-between; margin-bottom: 0; - margin-bottom: 20px; } } - &__inner > fieldset:not(:last-child) { - margin-bottom: 20px; + &__no-items { + position: absolute; + top: 60%; + left: 50%; } &__left { flex-basis: 50%; - margin-bottom: 20px; - - form { - width: 280px; - - @include responsive.apply-desktop { - width: 600px; - } - } } &__right { @@ -46,35 +40,22 @@ margin-top: 10px; } } - - &__achievement-item { - &:not(:last-child) { - margin-bottom: 12px; - } - } } .achievement { - &__first-row { - display: flex; - - // align-items: flex-start; - margin-bottom: 12px; - - > :first-child { - flex-grow: 1; - } - } + display: flex; + gap: 20px; + align-items: center; &__remove { - width: 155px; - margin-left: 10px; + margin-top: 15px; } } .invite { &__item { - margin-bottom: 12px; + align-items: center; + justify-content: space-between; } &__list { @@ -87,5 +68,14 @@ .vacancy { &__submit { display: block; + + &--text { + margin-top: 2px; + margin-right: 10px; + } } } + +.error { + color: var(--red) !important; +} diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.ts b/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.ts index 720cec5c7..555617607 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.ts +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.ts @@ -2,7 +2,7 @@ import { CommonModule } from "@angular/common"; import { Component, inject, Input } from "@angular/core"; -import { FormArray, FormGroup, ReactiveFormsModule } from "@angular/forms"; +import { FormArray, FormBuilder, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms"; import { InputComponent, ButtonComponent } from "@ui/components"; import { LinkCardComponent } from "@office/shared/link-card/link-card.component"; import { ControlErrorPipe } from "@corelib"; @@ -31,9 +31,13 @@ export class ProjectAchievementStepComponent { private readonly projectAchievementService = inject(ProjectAchievementsService); private readonly projectFormService = inject(ProjectFormService); + private readonly fb = inject(FormBuilder); readonly errorMessage = ErrorMessage; + // Состояние для показа полей ввода + public showInputFields = false; + // Получаем форму из сервиса get projectForm(): FormGroup { return this.projectFormService.getForm(); @@ -60,11 +64,53 @@ export class ProjectAchievementStepComponent { return this.projectFormService.editIndex; } + /** + * Проверяет, есть ли достижения для отображения + */ + get hasAchievements(): boolean { + return this.achievementsItems().length > 0 || this.achievements.length > 0; + } + + /** + * Показывает поля для ввода достижения + */ + showFields(): void { + this.showInputFields = true; + } + + /** + * Скрывает поля ввода и очищает их + */ + hideFields(): void { + this.showInputFields = false; + this.clearInputFields(); + } + + /** + * Очищает поля ввода + */ + private clearInputFields(): void { + this.projectForm.get("achievementsName")?.reset(); + this.projectForm.get("achievementsName")?.setValue(""); + + if (this.editIndex() !== null) { + this.projectFormService.editIndex.set(null); + } + } + /** * Добавление достижения */ - addAchievement(): void { - this.projectAchievementService.addAchievement(this.achievements, this.projectForm); + addAchievement(id?: number, achievementsName?: string, achievementsDate?: string): void { + // this.projectAchievementService.addAchievement(this.achievements, this.projectForm); + + this.achievements.push( + this.fb.group({ + achievementsName: [achievementsName ?? "", [Validators.required]], + achievementsDate: [achievementsDate ?? "", [Validators.required]], + id: [id], + }) + ); } /** @@ -72,6 +118,7 @@ export class ProjectAchievementStepComponent { * @param index - индекс достижения */ editAchievement(index: number): void { + this.showInputFields = true; this.projectAchievementService.editAchievement(index, this.achievements, this.projectForm); } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.html b/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.html index 202cfa3fc..7f1f07518 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.html +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.html @@ -3,8 +3,8 @@
- @if (partnerProgramFields.length) { @for (field of partnerProgramFields; track field.id) { +
@switch (field.fieldType) { @case ("text") { @if (additionalForm.get(field.name); as control) { @@ -89,7 +89,18 @@
} } - } } + } } @else { +
+

Пока ты не участвуешь ни в одной программе

+ + Программы + + + +
+ }
diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.scss index c1deba1bc..98c5dbd83 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.scss @@ -4,6 +4,8 @@ @use "styles/typography"; .project { + position: relative; + &__inner { width: 100%; margin-bottom: 25px; @@ -120,6 +122,18 @@ fieldset { margin-bottom: 10px; } + + &--no-items { + display: flex; + flex-direction: column; + gap: 10px; + } + } + + &__no-items { + position: absolute; + top: 50%; + left: 50%; } } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.ts b/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.ts index 972f62b68..ecc9e663b 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.ts +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.ts @@ -3,7 +3,12 @@ import { CommonModule } from "@angular/common"; import { Component, Input, OnInit, inject, ChangeDetectorRef } from "@angular/core"; import { FormGroup, ReactiveFormsModule } from "@angular/forms"; -import { InputComponent, CheckboxComponent, SelectComponent } from "@ui/components"; +import { + InputComponent, + CheckboxComponent, + SelectComponent, + ButtonComponent, +} from "@ui/components"; import { TextareaComponent } from "@ui/components/textarea/textarea.component"; import { SwitchComponent } from "@ui/components/switch/switch.component"; import { ControlErrorPipe } from "@corelib"; @@ -11,6 +16,8 @@ import { ErrorMessage } from "@error/models/error-message"; import { ToSelectOptionsPipe } from "projects/core/src/lib/pipes/options-transform.pipe"; import { ProjectAdditionalService } from "../../services/project-additional.service"; import { PartnerProgramFields } from "@office/models/partner-program-fields.model"; +import { RouterLink } from "@angular/router"; +import { IconComponent } from "@uilib"; @Component({ selector: "app-project-additional-step", @@ -21,12 +28,15 @@ import { PartnerProgramFields } from "@office/models/partner-program-fields.mode CommonModule, ReactiveFormsModule, InputComponent, + IconComponent, CheckboxComponent, SwitchComponent, SelectComponent, TextareaComponent, ControlErrorPipe, ToSelectOptionsPipe, + ButtonComponent, + RouterLink, ], }) export class ProjectAdditionalStepComponent implements OnInit { 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 8d237110c..29022f44d 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 @@ -73,17 +73,15 @@ } - } @if (step; as step) { + } @if (trl; as trl) {
- - @if (projectSteps$ | async; as steps) { - } @if (step | controlError: "required") { + @if (trl | controlError: "required") {
{{ errorMessage.VALIDATION_REQUIRED }}
@@ -121,9 +119,10 @@
} @if (implementationDeadline; as implementationDeadline) {
- + }
- } @if (trl; as trl) { -
- - @if ((trl | controlError: "required")) { -
- {{ errorMessage.VALIDATION_REQUIRED }} -
- } -
} @if (targetAudience; as targetAudience) {
- + Привязать проект к программе } } } } @if (presentationAddress; as presentationAddress) { diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.ts b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.ts index 7e6c7cdf1..da673be1b 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.ts +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.ts @@ -15,6 +15,7 @@ import { AsyncPipe, CommonModule } from "@angular/common"; import { ControlErrorPipe } from "@corelib"; import { ProjectFormService } from "../../services/project-form.service"; import { IconComponent } from "@uilib"; +import { generateTrlList } from "@utils/generate-trl-list"; @Component({ selector: "app-project-main-step", @@ -54,6 +55,7 @@ export class ProjectMainStepComponent implements OnInit, OnDestroy { readonly errorMessage = ErrorMessage; readonly trackList = trackProjectList; readonly directionList = directionProjectList; + readonly trlList = generateTrlList(9); // Получаем форму из сервиса get projectForm(): FormGroup { @@ -83,10 +85,6 @@ export class ProjectMainStepComponent implements OnInit, OnDestroy { return this.projectFormService.industry; } - get step() { - return this.projectFormService.step; - } - get description() { return this.projectFormService.description; } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.html b/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.html index 40816afaf..93ca4f185 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.html +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.html @@ -4,12 +4,14 @@
    @for (item of navItems; track $index) {
  • - project-icon + >

    {{ item.label }}

    diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.scss index ea3f6092b..27a4d96fa 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.scss @@ -5,7 +5,7 @@ .project { &__navigation { - padding: 10px 0; + padding: 22px 0; margin-bottom: 22px; border-bottom: 1px solid var(--grey-button); } @@ -25,15 +25,15 @@ &__item { display: flex; - flex-direction: column; + gap: 5px; align-items: center; cursor: pointer; } &__subtitle { - color: var(--grey-for-text); + color: var(--dark-grey); - @include typography.heading-4; + @include typography.body-12; &--active { color: var(--black); @@ -41,7 +41,7 @@ } &__icon { - opacity: 0.5; + opacity: 0.1; &--active { opacity: 1; diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.ts b/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.ts index 0e21ed92c..cc12a655d 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.ts +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.ts @@ -3,12 +3,15 @@ import { Component, inject, Output, EventEmitter } from "@angular/core"; import { EditStep, ProjectStepService } from "../../services/project-step.service"; import { navItems } from "projects/core/src/consts/navProjectItems"; +import { IconComponent } from "@uilib"; +import { CommonModule } from "@angular/common"; @Component({ selector: "app-project-navigation", templateUrl: "./project-navigation.component.html", styleUrl: "project-navigation.component.scss", standalone: true, + imports: [IconComponent, CommonModule], }) export class ProjectNavigationComponent { @Output() stepChange = new EventEmitter(); diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.html b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.html index 77ae94750..4566bc438 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.html +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.html @@ -2,7 +2,6 @@
    -
      }
    +
    - + diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss index e599c00db..c30ebf374 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss @@ -74,16 +74,11 @@ margin-bottom: 12px; } - &__link { - margin-bottom: 12px; - } - &__item { margin-bottom: 12px; } &__list { - width: 557px; width: 100%; margin-top: 10px; } @@ -94,7 +89,14 @@ margin-top: 10px; @include responsive.apply-desktop { - grid-template-columns: repeat(3, 350px); + grid-template-columns: repeat(5, 150px); + } + } + + &__link { + margin-bottom: 12px; + &--wrapper { + flex-basis: 50%; } } } diff --git a/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.html b/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.html index e3f6c1dd2..00f14859e 100644 --- a/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.html +++ b/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.html @@ -3,20 +3,13 @@ @if (collaborator) {
    -

    - {{ collaborator.firstName }} {{ collaborator.lastName }} -

    - -
    - @for (skill of collaborator.skills; track $index) { -
    -

    Категория - {{ skill.category.name }}

    -

    Навык - {{ skill.name }}

    -
    - } + +
    +

    + {{ collaborator.firstName }} {{ collaborator.lastName }} +

    +

    {{ collaborator.role }}

    - -

    Роль - {{ collaborator.role }}

    } diff --git a/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.scss b/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.scss index 95d0eb155..5d12d9227 100644 --- a/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.scss +++ b/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.scss @@ -3,11 +3,9 @@ @use "styles/typography"; .collaborator { - display: flex; - align-items: flex-start; - justify-content: space-between; - padding: 15px 10px; - background-color: var(--light-gray); + padding: 15px 24px; + background-color: white; + border: 1px solid var(--dark-grey); border-radius: var(--rounded-md); &__role { @@ -21,7 +19,15 @@ &__info { display: flex; flex-direction: column; - gap: 5px; + gap: 20px; + align-items: center; + text-align: center; + + &--text { + display: flex; + flex-direction: column; + gap: 5px; + } } &__skills { diff --git a/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.ts b/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.ts index 87d715b6c..01e0bc036 100644 --- a/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.ts +++ b/projects/social_platform/src/app/office/shared/collaborator-card/collaborator-card.component.ts @@ -5,6 +5,7 @@ import { Component, Input, OnInit } from "@angular/core"; import { FormBuilder, FormGroup, ReactiveFormsModule } from "@angular/forms"; import { ErrorMessage } from "@error/models/error-message"; import { Collaborator } from "@office/models/collaborator.model"; +import { AvatarComponent } from "@ui/components/avatar/avatar.component"; /** * Компонент карточки участника команды или проект @@ -20,7 +21,7 @@ import { Collaborator } from "@office/models/collaborator.model"; templateUrl: "./collaborator-card.component.html", styleUrl: "./collaborator-card.component.scss", standalone: true, - imports: [CommonModule, ReactiveFormsModule], + imports: [CommonModule, ReactiveFormsModule, AvatarComponent], }) export class CollaboratorCardComponent implements OnInit { constructor(private readonly fb: FormBuilder) { diff --git a/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.html b/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.html index 517202581..4342d9b66 100644 --- a/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.html +++ b/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.html @@ -3,6 +3,14 @@ @if (invite) {
    +
    +

    {{ invite.specialization }}

    + @if(invite.isAccepted === null) { +

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

    + } +
    + +

    {{ invite.user.firstName }} {{ invite.user.lastName }}

    @@ -10,30 +18,20 @@

    {{ invite.role }}

    - -

    Роль - {{ invite.specialization }}

    - - @if(invite.isAccepted === null) { -

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

    - }

    - + > --> - + + +
    } diff --git a/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.scss b/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.scss index efebbfcd7..c50e11962 100644 --- a/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.scss +++ b/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.scss @@ -19,8 +19,7 @@ } &__status { - margin: 5px 10px 0 auto; - color: var(--accent); + color: var(--dark-grey); } &__info { @@ -29,6 +28,14 @@ gap: 5px; } + &__top { + display: flex; + gap: 15px; + padding-bottom: 5px; + margin-bottom: 15px; + border-bottom: 1px solid var(--dark-grey); + } + &__warning-modal { display: flex; flex-direction: column; diff --git a/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.ts b/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.ts index da0c96cc4..d4e982992 100644 --- a/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.ts +++ b/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.ts @@ -8,6 +8,7 @@ import { Invite } from "@models/invite.model"; import { IconComponent, ButtonComponent, SelectComponent, InputComponent } from "@ui/components"; import { ModalComponent } from "@ui/components/modal/modal.component"; import { rolesMembersList } from "projects/core/src/consts/list-roles-members"; +import { AvatarComponent } from "@ui/components/avatar/avatar.component"; /** * Компонент карточки приглашения в команду или проект @@ -39,6 +40,7 @@ import { rolesMembersList } from "projects/core/src/consts/list-roles-members"; ControlErrorPipe, ReactiveFormsModule, InputComponent, + AvatarComponent, ], }) export class InviteCardComponent implements OnInit { diff --git a/projects/social_platform/src/app/ui/components/button/button.component.html b/projects/social_platform/src/app/ui/components/button/button.component.html index 18145da6a..d9e19088c 100644 --- a/projects/social_platform/src/app/ui/components/button/button.component.html +++ b/projects/social_platform/src/app/ui/components/button/button.component.html @@ -21,7 +21,7 @@ [disabled]="disabled" > @if (loader) { - + } @else { diff --git a/projects/social_platform/src/app/ui/components/button/button.component.scss b/projects/social_platform/src/app/ui/components/button/button.component.scss index 799343d21..5580a4cb2 100644 --- a/projects/social_platform/src/app/ui/components/button/button.component.scss +++ b/projects/social_platform/src/app/ui/components/button/button.component.scss @@ -92,13 +92,14 @@ text-align: center; cursor: pointer; background-color: transparent; - border: 2px solid var(--accent); + border: 1px solid var(--accent); border-radius: var(--rounded-xl); transition: all 0.2s; &:hover { color: var(--accent-light); border-color: var(--accent-light); + box-shadow: -2px 3px 3px rgb(51 51 51 / 20%); } &.button--red { diff --git a/projects/social_platform/src/app/ui/components/input/input.component.scss b/projects/social_platform/src/app/ui/components/input/input.component.scss index f44fa86b3..7a7c613bd 100644 --- a/projects/social_platform/src/app/ui/components/input/input.component.scss +++ b/projects/social_platform/src/app/ui/components/input/input.component.scss @@ -12,7 +12,7 @@ color: var(--black); background-color: var(--white); border: 1px solid var(--gray); - border-radius: var(--rounded-lg); + border-radius: var(--rounded-xl); outline: none; transition: all 0.2s; diff --git a/projects/social_platform/src/app/ui/components/input/input.component.ts b/projects/social_platform/src/app/ui/components/input/input.component.ts index 80734f233..14ccf55c0 100644 --- a/projects/social_platform/src/app/ui/components/input/input.component.ts +++ b/projects/social_platform/src/app/ui/components/input/input.component.ts @@ -45,7 +45,7 @@ export class InputComponent implements OnInit, ControlValueAccessor { @Input() placeholder = ""; /** Тип поля ввода */ - @Input() type: "text" | "password" | "email" | "tel" = "text"; + @Input() type: "text" | "password" | "email" | "tel" | "date" = "text"; /** Состояние ошибки */ @Input() error = false; diff --git a/projects/social_platform/src/app/ui/components/select/select.component.scss b/projects/social_platform/src/app/ui/components/select/select.component.scss index 2a0471e56..b9d248b40 100644 --- a/projects/social_platform/src/app/ui/components/select/select.component.scss +++ b/projects/social_platform/src/app/ui/components/select/select.component.scss @@ -19,7 +19,7 @@ cursor: pointer; background-color: var(--white); border: 1px solid var(--gray); - border-radius: var(--rounded-lg); + border-radius: var(--rounded-xl); outline: none; &--placeholder { diff --git a/projects/social_platform/src/app/utils/generate-trl-list.ts b/projects/social_platform/src/app/utils/generate-trl-list.ts new file mode 100644 index 000000000..ce149d35e --- /dev/null +++ b/projects/social_platform/src/app/utils/generate-trl-list.ts @@ -0,0 +1,38 @@ +/** @format */ + +interface trlListElement { + id: number; // порядковый номер в массиве + value: number; // строка, которую будем показывать в UI + label: string; // то же самое, что и value (можно использовать обе подписи) +} + +/** + * Генерирует массив годов. + * + * @param amount – сколько лет нужно вывести. + * Считаем, что список должен начинаться с «текущий‑year‑amount+1» и + * заканчиваться текущим календарным годом. + * @returns массив объектов вида { id, value, label } + * + * Пример: generateTrlList(3) + * [ + * { id: 0, value: 1, label: '1' }, + * { id: 1, value: 2, label: '2' }, + * { id: 2, value: 3, label: '3' } + * ] + */ +export const generateTrlList = (amount: number): trlListElement[] => { + if (amount <= 0) return []; + + const list: trlListElement[] = []; + + for (let i = 1; i < amount; i++) { + list.push({ + id: i, + value: i, + label: `${i}`, + }); + } + + return list; +}; diff --git a/projects/social_platform/src/assets/icons/svg/achievements.svg b/projects/social_platform/src/assets/icons/svg/achievements.svg new file mode 100644 index 000000000..1cdbd690f --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/achievements.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/svg/additional.svg b/projects/social_platform/src/assets/icons/svg/additional.svg new file mode 100644 index 000000000..546963d74 --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/additional.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/svg/contacts.svg b/projects/social_platform/src/assets/icons/svg/contacts.svg new file mode 100644 index 000000000..ccd2d6175 --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/contacts.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/svg/error.svg b/projects/social_platform/src/assets/icons/svg/error.svg index 0c149fe7a..5201c9ef4 100644 --- a/projects/social_platform/src/assets/icons/svg/error.svg +++ b/projects/social_platform/src/assets/icons/svg/error.svg @@ -1,3 +1,3 @@ - - + + diff --git a/projects/social_platform/src/assets/icons/svg/main.svg b/projects/social_platform/src/assets/icons/svg/main.svg new file mode 100644 index 000000000..204d9474e --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/main.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/svg/sad-smile.svg b/projects/social_platform/src/assets/icons/svg/sad-smile.svg new file mode 100644 index 000000000..c9aea29be --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/sad-smile.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/svg/team.svg b/projects/social_platform/src/assets/icons/svg/team.svg new file mode 100644 index 000000000..806ca787f --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/team.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/svg/vacancies.svg b/projects/social_platform/src/assets/icons/svg/vacancies.svg new file mode 100644 index 000000000..871e9a4b3 --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/vacancies.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg index 21d60ce88..39e6f032e 100644 --- a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg +++ b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/projects/social_platform/src/styles/_typography.scss b/projects/social_platform/src/styles/_typography.scss index 2142c754e..c396582c8 100644 --- a/projects/social_platform/src/styles/_typography.scss +++ b/projects/social_platform/src/styles/_typography.scss @@ -66,9 +66,9 @@ @mixin heading-1 { font-family: Mont, sans-serif; - font-size: 36px; + font-size: 18px; font-style: normal; - font-weight: 700; + font-weight: 400; line-height: 150%; } @@ -76,11 +76,23 @@ @include heading-1; } +@mixin heading-1-bold { + font-family: Mont, sans-serif; + font-size: 18px; + font-style: normal; + font-weight: 600; + line-height: 150%; +} + +.text-heading-1-bold { + @include heading-1-bold; +} + @mixin heading-2 { font-family: Mont, sans-serif; - font-size: 28px; + font-size: 16px; font-style: normal; - font-weight: 700; + font-weight: 400; line-height: 150%; } @@ -88,6 +100,18 @@ @include heading-2; } +@mixin heading-2-bold { + font-family: Mont, sans-serif; + font-size: 16px; + font-style: normal; + font-weight: 600; + line-height: 150%; +} + +.text-heading-2-bold { + @include heading-2-bold; +} + @mixin heading-3 { font-family: Mont, sans-serif; font-size: 24px; @@ -188,7 +212,7 @@ font-family: Mont, sans-serif; font-size: 12px; font-style: normal; - font-weight: 600; + font-weight: 400; line-height: 130%; } @@ -196,6 +220,18 @@ @include body-12; } +@mixin body-12-bold { + font-family: Mont, sans-serif; + font-size: 12px; + font-style: normal; + font-weight: 600; + line-height: 130%; +} + +.text-body-12-bold { + @include body-12-bold; +} + @mixin body-10 { font-family: Mont, sans-serif; font-size: 10px; From 8e010cea7f1400a1c3206430b0f0ebe357220efe Mon Sep 17 00:00:00 2001 From: Awakich Date: Thu, 4 Sep 2025 16:31:04 +0300 Subject: [PATCH 028/126] add styles to team block --- .../shared/project-team-step/project-team-step.component.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss index c30ebf374..1ed9f2492 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss @@ -95,6 +95,7 @@ &__link { margin-bottom: 12px; + &--wrapper { flex-basis: 50%; } From b5cfbb95f6f692f312b5979f9dfb66a1d5a06c73 Mon Sep 17 00:00:00 2001 From: Awakich Date: Fri, 5 Sep 2025 22:13:49 +0300 Subject: [PATCH 029/126] change design of main stage, contacts, vacancies & upload-file, textarea, input, select components --- .../chat-direct/chat-direct.component.scss | 2 +- .../src/app/office/chat/chat.component.scss | 2 +- .../feed/filter/feed-filter.component.scss | 2 +- .../closed-vacancy.component.scss | 6 +- .../new-project/new-project.component.scss | 2 +- .../open-vacancy/open-vacancy.component.scss | 6 +- .../src/app/office/models/project.model.ts | 2 - .../stage-zero/stage-zero.component.scss | 2 +- .../user-type-card.component.scss | 2 +- .../profile/detail/main/main.component.scss | 2 +- .../detail/profile-detail.component.scss | 4 +- .../office/profile/edit/edit.component.scss | 2 +- .../program/detail/main/main.component.scss | 2 +- .../detail/projects/projects.component.scss | 4 +- .../shared/news-card/news-card.component.scss | 6 +- .../program-card/program-card.component.scss | 2 +- .../program-head/program-head.component.scss | 2 +- .../rating-card/rating-card.component.scss | 6 +- .../projects/detail/info/info.component.scss | 2 +- .../project-member-card.component.scss | 2 +- .../office/projects/edit/edit.component.scss | 7 +- .../project-achievement-step.component.html | 12 +- .../project-achievement-step.component.scss | 11 +- .../project-achievement-step.component.ts | 4 +- .../project-additional-step.component.scss | 2 +- .../project-contacts-step.component.html | 50 ++- .../project-contacts-step.component.scss | 33 +- .../project-main-step.component.html | 358 ++++++++++-------- .../project-main-step.component.scss | 157 ++------ .../project-main-step.component.ts | 2 +- .../project-navigation.component.html | 8 +- .../project-navigation.component.scss | 11 +- .../project-team-step.component.html | 163 ++++---- .../project-team-step.component.scss | 20 +- .../project-team-step.component.ts | 9 + .../project-vacancy-step.component.html | 34 +- .../project-vacancy-step.component.scss | 24 +- .../project-vacancy-step.component.ts | 8 + .../office/projects/list/list.component.scss | 2 +- .../office/projects/projects.component.scss | 2 +- .../shared/carousel/carousel.component.scss | 6 +- .../shared/img-card/img-card.component.scss | 4 +- .../invite-card/invite-card.component.html | 28 +- .../invite-card/invite-card.component.scss | 13 +- .../member-card/member-card.component.scss | 2 +- .../shared/news-card/news-card.component.scss | 6 +- .../shared/news-form/news-form.component.scss | 2 +- .../project-card/project-card.component.scss | 2 +- .../skills-basket.component.html | 4 +- .../skills-basket.component.scss | 17 +- .../vacancy-card/vacancy-card.component.html | 43 ++- .../vacancy-card/vacancy-card.component.scss | 55 ++- .../vacancy-card/vacancy-card.component.ts | 9 +- .../vacancies/detail/info/info.component.scss | 10 +- .../filter/vacancy-filter.component.scss | 2 +- .../office/vacancy/send/send.component.html | 6 +- .../office/vacancy/send/send.component.scss | 2 +- .../avatar-control.component.html | 2 +- .../app/ui/components/bar/bar.component.scss | 2 +- .../components/button/button.component.scss | 12 +- .../ui/components/input/input.component.html | 6 +- .../ui/components/input/input.component.scss | 27 +- .../ui/components/input/input.component.ts | 24 +- .../ui/components/modal/modal.component.scss | 2 +- .../components/select/select.component.html | 7 +- .../components/select/select.component.scss | 39 +- .../ui/components/select/select.component.ts | 2 + .../textarea/textarea.component.html | 5 +- .../textarea/textarea.component.scss | 21 +- .../components/textarea/textarea.component.ts | 2 + .../upload-file/upload-file.component.html | 13 +- .../upload-file/upload-file.component.scss | 25 +- .../upload-file/upload-file.component.ts | 3 +- .../src/assets/icons/svg/edit-pen-old.svg | 3 + .../src/assets/icons/svg/file-success.svg | 3 + .../src/assets/icons/svg/file.svg | 7 +- .../assets/icons/symbol/svg/sprite.css.svg | 2 +- .../social_platform/src/styles/_rounded.scss | 3 +- .../src/styles/pages/_members.scss | 2 +- 79 files changed, 764 insertions(+), 636 deletions(-) create mode 100644 projects/social_platform/src/assets/icons/svg/edit-pen-old.svg create mode 100644 projects/social_platform/src/assets/icons/svg/file-success.svg diff --git a/projects/social_platform/src/app/office/chat/chat-direct/chat-direct/chat-direct.component.scss b/projects/social_platform/src/app/office/chat/chat-direct/chat-direct/chat-direct.component.scss index 42ee3c95b..0923e83ff 100644 --- a/projects/social_platform/src/app/office/chat/chat-direct/chat-direct/chat-direct.component.scss +++ b/projects/social_platform/src/app/office/chat/chat-direct/chat-direct/chat-direct.component.scss @@ -15,7 +15,7 @@ flex-grow: 1; overflow: hidden; border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); } &__header { diff --git a/projects/social_platform/src/app/office/chat/chat.component.scss b/projects/social_platform/src/app/office/chat/chat.component.scss index 1902a06dc..311c45cec 100644 --- a/projects/social_platform/src/app/office/chat/chat.component.scss +++ b/projects/social_platform/src/app/office/chat/chat.component.scss @@ -6,6 +6,6 @@ &__list { overflow: hidden; border: 1px solid var(--grey-button); - border-radius: 15px; + border-radius: var(--rounded-xl); } } diff --git a/projects/social_platform/src/app/office/feed/filter/feed-filter.component.scss b/projects/social_platform/src/app/office/feed/filter/feed-filter.component.scss index c2b1942cc..f8b2ccc4d 100644 --- a/projects/social_platform/src/app/office/feed/filter/feed-filter.component.scss +++ b/projects/social_platform/src/app/office/feed/filter/feed-filter.component.scss @@ -15,7 +15,7 @@ margin-bottom: 20px; background-color: var(--white); border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); @include responsive.apply-desktop { display: flex; diff --git a/projects/social_platform/src/app/office/feed/shared/closed-vacancy/closed-vacancy.component.scss b/projects/social_platform/src/app/office/feed/shared/closed-vacancy/closed-vacancy.component.scss index e49466213..1a2c93761 100644 --- a/projects/social_platform/src/app/office/feed/shared/closed-vacancy/closed-vacancy.component.scss +++ b/projects/social_platform/src/app/office/feed/shared/closed-vacancy/closed-vacancy.component.scss @@ -5,7 +5,7 @@ padding: 20px; background-color: var(--white); border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); &__head { margin-bottom: 10px; @@ -68,7 +68,7 @@ justify-content: space-between; padding: 10px 20px; border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); } &__job { @@ -77,7 +77,7 @@ margin-bottom: 15px; text-align: center; border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); @include typography.bold-body-14; diff --git a/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.scss b/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.scss index 65ea011f9..af37ad8c3 100644 --- a/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.scss +++ b/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.scss @@ -8,7 +8,7 @@ padding: 20px; background-color: var(--white); border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); &__title { display: flex; diff --git a/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.scss b/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.scss index 52b2f16bc..f7501580b 100644 --- a/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.scss +++ b/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.scss @@ -33,7 +33,7 @@ padding: 20px; background-color: var(--white); border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); &__head { margin-bottom: 10px; @@ -177,7 +177,7 @@ justify-content: space-between; padding: 10px 20px; border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); } &__job { @@ -187,7 +187,7 @@ text-align: center; cursor: pointer; border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); @include typography.bold-body-14; diff --git a/projects/social_platform/src/app/office/models/project.model.ts b/projects/social_platform/src/app/office/models/project.model.ts index 4745c4b1c..e02d8ac25 100644 --- a/projects/social_platform/src/app/office/models/project.model.ts +++ b/projects/social_platform/src/app/office/models/project.model.ts @@ -36,7 +36,6 @@ export class Project { actuality!: string; problem!: string; region!: string; - step!: number; shortDescription!: string; achievements!: { id: number; title: string; status: string }[]; industry!: number; @@ -62,7 +61,6 @@ export class Project { id: 0, name: "string", region: "sdf", - step: 1, targetAudience: "", implementationDeadline: "", actuality: "", diff --git a/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.scss b/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.scss index 574e956cc..0b6c9d5c9 100644 --- a/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.scss +++ b/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.scss @@ -135,7 +135,7 @@ padding: 12px; overflow: hidden; border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); } &__text { diff --git a/projects/social_platform/src/app/office/onboarding/user-type-card/user-type-card.component.scss b/projects/social_platform/src/app/office/onboarding/user-type-card/user-type-card.component.scss index 483e04bdc..689280142 100644 --- a/projects/social_platform/src/app/office/onboarding/user-type-card/user-type-card.component.scss +++ b/projects/social_platform/src/app/office/onboarding/user-type-card/user-type-card.component.scss @@ -5,7 +5,7 @@ padding: 12px; cursor: pointer; border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); @include responsive.apply-desktop { padding: 20px; diff --git a/projects/social_platform/src/app/office/profile/detail/main/main.component.scss b/projects/social_platform/src/app/office/profile/detail/main/main.component.scss index 6d73578e4..027d3b496 100644 --- a/projects/social_platform/src/app/office/profile/detail/main/main.component.scss +++ b/projects/social_platform/src/app/office/profile/detail/main/main.component.scss @@ -74,7 +74,7 @@ $section-padding: 24px; padding: 24px; background-color: var(--white); border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); h3 { margin-bottom: 12px; diff --git a/projects/social_platform/src/app/office/profile/detail/profile-detail.component.scss b/projects/social_platform/src/app/office/profile/detail/profile-detail.component.scss index bcd773f6a..b8449ed56 100644 --- a/projects/social_platform/src/app/office/profile/detail/profile-detail.component.scss +++ b/projects/social_platform/src/app/office/profile/detail/profile-detail.component.scss @@ -18,7 +18,7 @@ $detail-bar-mb: 12px; height: $detail-bar-height; margin-bottom: $detail-bar-mb; border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); } &__info { @@ -154,7 +154,7 @@ $detail-bar-mb: 12px; padding: 0 20px; margin-top: 15px; border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); p { text-align: center; diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.scss b/projects/social_platform/src/app/office/profile/edit/edit.component.scss index 09b3354d1..618568c30 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.scss +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.scss @@ -252,7 +252,7 @@ padding: 12px; overflow: hidden; border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); } &__text { diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.scss b/projects/social_platform/src/app/office/program/detail/main/main.component.scss index be57ed88c..099dd15de 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.scss +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.scss @@ -40,7 +40,7 @@ padding: 24px; background-color: var(--white); border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); } &__info { diff --git a/projects/social_platform/src/app/office/program/detail/projects/projects.component.scss b/projects/social_platform/src/app/office/program/detail/projects/projects.component.scss index 97ad6ef38..45b26db4c 100644 --- a/projects/social_platform/src/app/office/program/detail/projects/projects.component.scss +++ b/projects/social_platform/src/app/office/program/detail/projects/projects.component.scss @@ -46,7 +46,7 @@ padding: 20px; background-color: var(--white); border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); } } @@ -129,7 +129,7 @@ cursor: pointer; background-color: var(--white); border: 1px solid var(--medium-medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); @include responsive.apply-desktop { display: none; diff --git a/projects/social_platform/src/app/office/program/detail/shared/news-card/news-card.component.scss b/projects/social_platform/src/app/office/program/detail/shared/news-card/news-card.component.scss index 0ebf7ea29..9b55f8d0e 100644 --- a/projects/social_platform/src/app/office/program/detail/shared/news-card/news-card.component.scss +++ b/projects/social_platform/src/app/office/program/detail/shared/news-card/news-card.component.scss @@ -5,7 +5,7 @@ padding: 20px; background-color: var(--white); border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); &__head { display: flex; @@ -34,7 +34,7 @@ padding: 20px 0; background-color: var(--white); border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); } &__option { @@ -150,7 +150,7 @@ height: 75px; color: var(--accent); background-color: var(--white); - border-radius: 15px; + border-radius: var(--rounded-xl); transition: transform 0.1s ease-in-out; transform: translate(-50%, -50%) scale(0); diff --git a/projects/social_platform/src/app/office/program/shared/program-card/program-card.component.scss b/projects/social_platform/src/app/office/program/shared/program-card/program-card.component.scss index 78356d3ea..9cbbfd983 100644 --- a/projects/social_platform/src/app/office/program/shared/program-card/program-card.component.scss +++ b/projects/social_platform/src/app/office/program/shared/program-card/program-card.component.scss @@ -6,7 +6,7 @@ position: relative; padding: 24px 15px; background-color: var(--white); - border-radius: 15px; + border-radius: var(--rounded-xl); @include responsive.apply-desktop { border: 1px solid var(--medium-grey-for-outline); diff --git a/projects/social_platform/src/app/office/program/shared/program-head/program-head.component.scss b/projects/social_platform/src/app/office/program/shared/program-head/program-head.component.scss index 669eac5af..e63bc06f5 100644 --- a/projects/social_platform/src/app/office/program/shared/program-head/program-head.component.scss +++ b/projects/social_platform/src/app/office/program/shared/program-head/program-head.component.scss @@ -98,7 +98,7 @@ height: 60px; padding: 12.5px 25px; border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); outline: none; } diff --git a/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.scss b/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.scss index 588efe7c4..e43ee4465 100644 --- a/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.scss +++ b/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.scss @@ -8,13 +8,13 @@ } .card { - border-radius: 15px; + border-radius: var(--rounded-xl); &__upper { padding: 14px; background-color: var(--white); border: 1px solid var(--accent); - border-radius: 15px; + border-radius: var(--rounded-xl); @include responsive.apply-desktop { min-height: 550px; @@ -27,7 +27,7 @@ display: flex; flex-direction: column; gap: 20px; - border-radius: 15px; + border-radius: var(--rounded-xl); app-button ::ng-deep .button--inline { min-height: 38px; diff --git a/projects/social_platform/src/app/office/projects/detail/info/info.component.scss b/projects/social_platform/src/app/office/projects/detail/info/info.component.scss index d4e89cb37..fa1c9dc31 100644 --- a/projects/social_platform/src/app/office/projects/detail/info/info.component.scss +++ b/projects/social_platform/src/app/office/projects/detail/info/info.component.scss @@ -67,7 +67,7 @@ padding: 24px; background-color: var(--white); border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); } &__info { diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-member-card/project-member-card.component.scss b/projects/social_platform/src/app/office/projects/detail/shared/project-member-card/project-member-card.component.scss index b5ec99de5..0a9aab80c 100644 --- a/projects/social_platform/src/app/office/projects/detail/shared/project-member-card/project-member-card.component.scss +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-member-card/project-member-card.component.scss @@ -40,7 +40,7 @@ padding: 20px 14px; background-color: var(--white); border: 1px solid var(--grey-button); - border-radius: 15px; + border-radius: var(--rounded-xl); transform: translateX(-50%); &__item { diff --git a/projects/social_platform/src/app/office/projects/edit/edit.component.scss b/projects/social_platform/src/app/office/projects/edit/edit.component.scss index f74f3c60d..cc47a2311 100644 --- a/projects/social_platform/src/app/office/projects/edit/edit.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/edit.component.scss @@ -5,9 +5,8 @@ .project { position: relative; - height: 100%; - min-height: 800px; - padding: 100px 24px; + + padding: 80px 0px; background-color: var(--white); border-radius: var(--rounded-md); @@ -21,7 +20,7 @@ justify-content: space-between; width: 72%; background-color: var(--white); - border-radius: var(--rounded-xl); + border-radius: var(--rounded-xxl); } &__title { diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.html b/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.html index 1fbee5b94..f9cbe5b4c 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.html +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.html @@ -44,10 +44,16 @@
-
- +
+ Добавить достижение - + @if (!achievements.length) { diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.scss index c8aa65544..f95a895bb 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.scss @@ -20,7 +20,7 @@ &__no-items { position: absolute; - top: 60%; + bottom: 0%; left: 50%; } @@ -30,15 +30,6 @@ &__right { flex-basis: 50%; - - :first-child & :not(span, fieldset, label, h4, p, i) { - margin-top: 26px; - margin-bottom: 10px; - } - - :last-child & :not(i, span) { - margin-top: 10px; - } } } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.ts b/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.ts index 555617607..953c393d8 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.ts +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.ts @@ -102,8 +102,6 @@ export class ProjectAchievementStepComponent { * Добавление достижения */ addAchievement(id?: number, achievementsName?: string, achievementsDate?: string): void { - // this.projectAchievementService.addAchievement(this.achievements, this.projectForm); - this.achievements.push( this.fb.group({ achievementsName: [achievementsName ?? "", [Validators.required]], @@ -111,6 +109,8 @@ export class ProjectAchievementStepComponent { id: [id], }) ); + + this.projectAchievementService.addAchievement(this.achievements, this.projectForm); } /** diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.scss index 98c5dbd83..82c53731b 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.scss @@ -132,7 +132,7 @@ &__no-items { position: absolute; - top: 50%; + bottom: 0%; left: 50%; } } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-contacts-step/project-contacts-step.component.html b/projects/social_platform/src/app/office/projects/edit/shared/project-contacts-step/project-contacts-step.component.html index 5b32de1ac..90eaeb1fa 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-contacts-step/project-contacts-step.component.html +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-contacts-step/project-contacts-step.component.html @@ -1,7 +1,7 @@
- @if(link; as link){ + -
- - Добавить ещё одну ссылку +
+ + + + Добавить партнера +
-
    +
    + + + + Добавить ресурс + +
    + + @if (!links.length) { + + }
diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-contacts-step/project-contacts-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-contacts-step/project-contacts-step.component.scss index d6a14b7d5..fc941cb3b 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-contacts-step/project-contacts-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-contacts-step/project-contacts-step.component.scss @@ -4,9 +4,10 @@ @use "styles/typography"; .project { + position: relative; + &__inner { width: 100%; - margin-bottom: 25px; @include responsive.apply-desktop { display: flex; @@ -17,40 +18,18 @@ } } - &__inner > fieldset:not(:last-child) { - margin-bottom: 20px; - } - &__left { flex-basis: 50%; - margin-bottom: 20px; - - form { - width: 280px; - - @include responsive.apply-desktop { - width: 600px; - } - } } &__right { flex-basis: 50%; - - :first-child & :not(span, fieldset, label, h4, p, i) { - margin-top: 26px; - margin-bottom: 10px; - } - - :last-child & :not(i, span) { - margin-top: 10px; - } } - &__add-link { - i { - margin-left: 10px; - } + &__no-items { + position: absolute; + bottom: 0%; + left: 50%; } } 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 29022f44d..ff75a0a52 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 @@ -1,77 +1,69 @@
-
-
-
- @if (imageAddress; as imageAddress) { -
- - @if ((imageAddress | controlError: "required") && projSubmitInitiated) { -
- {{ errorMessage.EMPTY_AVATAR }} -
- } +
+
+ @if (imageAddress; as imageAddress) { +
+ + + @if ((imageAddress | controlError: "required") && projSubmitInitiated) { +
+ {{ errorMessage.EMPTY_AVATAR }}
}
- -
- @if (name; as name) { -
- - - @if ((name | controlError: "required") && projSubmitInitiated) { -
- {{ errorMessage.VALIDATION_REQUIRED }} -
- } -
- } @if (region; as region) { -
- - - @if ((region | controlError: "required") && projSubmitInitiated) { -
- {{ errorMessage.VALIDATION_REQUIRED }} -
- } -
- } -
-
- -
- @if (industry; as industry) { -
- - @if (industries$ | async; as industries) { - - } @if (industry | controlError: "required") { -
- {{ errorMessage.VALIDATION_REQUIRED }} -
- } + } @if (presentationAddress; as presentationAddress) { +
+ + + + +

+ Презентации формата .PDF
+ или .PPTX весом до 50МБ +

+ @if (presentationAddress | controlError: "required") { +

Загрузите файл

+ } +
+
+
+ } @if (coverImageAddress; as coverImageAddress) { +
+ + + + +

+ Презентации формата .jpg, .jpeg, .png +
Размер изображения 1280 x 230 +

+ @if (coverImageAddress | controlError: "required") { +

Загрузите файл

+ } +
+
} @if (trl; as trl) {
@@ -82,82 +74,82 @@ [options]="trlList" > @if (trl | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
}
- } @if (description; as description) { + } +
+ +
+ @if (name; as name) {
- - - @if ((description | controlError: "required") && projSubmitInitiated) { -
+ + + @if ((name | controlError: "required") && projSubmitInitiated) { +
{{ errorMessage.VALIDATION_REQUIRED }}
}
- } @if (actuality; as actuality) { + } @if (region; as region) {
- + - @if ((actuality | controlError: "required")) { -
+ @if ((region | controlError: "required") && projSubmitInitiated) { +
+ {{ errorMessage.VALIDATION_REQUIRED }} +
+ } +
+ } @if (industry; as industry) { +
+ + @if (industries$ | async; as industries) { + + } @if (industry | controlError: "required") { +
{{ errorMessage.VALIDATION_REQUIRED }}
}
} @if (implementationDeadline; as implementationDeadline) {
- + @if ((implementationDeadline | controlError: "required") && projSubmitInitiated) { -
- {{ errorMessage.VALIDATION_REQUIRED }} -
- } -
- } @if (problem; as problem) { -
- - - @if ((problem | controlError: "required") && projSubmitInitiated) { -
- {{ errorMessage.VALIDATION_REQUIRED }} -
- } -
- } @if (targetAudience; as targetAudience) { -
- - @if ((targetAudience | controlError: "required")) { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -166,8 +158,77 @@
-
- @if (!isProjectBoundToProgram) { @if (authService.profile | async; as profile) { @if (profile.id + @if (problem; as problem) { +
+ + + @if ((problem | controlError: "required") && projSubmitInitiated) { +
+ {{ errorMessage.VALIDATION_REQUIRED }} +
+ } +
+ } @if (description; as description) { +
+ + + @if ((description | controlError: "required") && projSubmitInitiated) { +
+ {{ errorMessage.VALIDATION_REQUIRED }} +
+ } +
+ } + +
+ @if (actuality; as actuality) { +
+ + + @if ((actuality | controlError: "required")) { +
+ {{ errorMessage.VALIDATION_REQUIRED }} +
+ } +
+ } @if (targetAudience; as targetAudience) { +
+ + + @if ((targetAudience | controlError: "required")) { +
+ {{ errorMessage.VALIDATION_REQUIRED }} +
+ } +
+ } +
+ +
+ + + + Добавить краткосрочную цель проекта + + + + + Добавить ссылку на контакты и сообщества + +
diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.scss index 225c80375..943b5aa4f 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.scss @@ -6,150 +6,46 @@ .project { &__inner { width: 100%; - margin-bottom: 25px; - - @include responsive.apply-desktop { - display: flex; - gap: 90px; - justify-content: space-between; - margin-bottom: 0; - margin-bottom: 20px; - } } - &__inner > fieldset:not(:last-child) { + &__grid { + display: grid; + align-items: center; + grid-gap: 20px; margin-bottom: 20px; } - &__left { - flex-basis: 50%; - margin-bottom: 20px; - - form { - width: 280px; - - @include responsive.apply-desktop { - width: 600px; - } - } - } - - &__form { + &__options { display: flex; flex-direction: column; - padding: 15px; - color: var(--black); - background-color: var(--white); - border: 1px solid var(--grey-button); - border-radius: var(--rounded-md); - - @include responsive.apply-desktop { - flex-direction: column; - align-items: flex-start; - padding: 24px; - } + gap: 35px; + margin-top: 35px; } &__info { display: flex; flex-direction: column; - @include responsive.apply-desktop { - flex-direction: row; - justify-content: space-between; - } - } - - &__right { - flex-basis: 50%; - - :first-child & :not(span, fieldset, label, h4, p, i) { - margin-top: 26px; - margin-bottom: 10px; - } - - :last-child & :not(i, span) { - margin-top: 10px; - } - } - - &__generals { - margin-bottom: 10px; - - :first-child { - margin-bottom: 10px; - } - } - - &__tags { - margin-bottom: 10px; - } - - &__additional { - &-wrapper { + &--additional { display: flex; - flex-direction: column; + align-items: center; gap: 20px; - width: 100%; - - ::ng-deep { - app-input { - input { - color: var(--black) !important; - - @include typography.body-10; - } - } - - app-textarea { - textarea { - color: var(--black) !important; - @include typography.body-10; - } - } - - app-select { - .field__input { - color: var(--black) !important; - - &--placeholder { - color: var(--grey-for-text) !important; - } - - @include typography.body-10; - } - } - } - - @include responsive.apply-desktop { - width: 60%; - - ::ng-deep { - app-input { - input { - @include typography.body-14; - } - } - - app-textarea { - textarea { - @include typography.body-14; - } - } - - app-select { - .field__input { - @include typography.body-14; - } - } - } + fieldset { + flex-basis: 50%; } } + } - fieldset { - margin-bottom: 10px; - } + &__problem, &__description { + margin-bottom: 12px; + } + + &__form { + color: var(--black); + background-color: var(--white); + border: 1px solid var(--grey-button); + border-radius: var(--rounded-md); } &__image { @@ -161,10 +57,6 @@ } } - &__avatar { - margin-bottom: 20px; - } - &__file { min-width: 0; } @@ -179,8 +71,13 @@ &__slides-text { max-width: 275px; margin-top: 12px; - color: var(--dark-grey); + color: var(--black); text-align: center; + opacity: 0.3; + + &:hover { + opacity: 1; + } } &__slides-error { diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.ts b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.ts index da673be1b..bacfdff99 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.ts +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.ts @@ -1,7 +1,7 @@ /** @format */ import { Component, Input, Output, EventEmitter, inject, OnInit, OnDestroy } from "@angular/core"; -import { FormGroup, ReactiveFormsModule } from "@angular/forms"; +import { FormArray, FormGroup, ReactiveFormsModule } from "@angular/forms"; import { AuthService } from "@auth/services"; import { ErrorMessage } from "@error/models/error-message"; import { directionProjectList } from "projects/core/src/consts/list-direction-project"; diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.html b/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.html index 93ca4f185..b233a9513 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.html +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.html @@ -3,12 +3,16 @@
- - - -
-
- - -
- @if (role; as role) { -
- - - @if ((role | controlError: "required") && inviteSubmitInitiated()) { -
- {{ errorMessage.VALIDATION_REQUIRED }} -
- } -
- } @if (link; as link) { - + @if (showFields) { +
+
+ + @if (link; as link) { +
+ + + @if ((link | controlError: "required") && inviteSubmitInitiated()) { +
+ {{ errorMessage.VALIDATION_REQUIRED }} +
+ } @if ((link | controlError: "pattern") && inviteSubmitInitiated()) { +
+ {{ errorMessage.VALIDATION_PROFILE_LINK }} +
+ } @if (inviteNotExistingError() && inviteSubmitInitiated()) { +
+ {{ errorMessage.USER_NOT_EXISTING }} +
+ } +
+ } @if (role; as role) { +
+ + + @if ((role | controlError: "required") && inviteSubmitInitiated()) { +
+ {{ errorMessage.VALIDATION_REQUIRED }} +
} +
+ } -
- Отправить - -
- + --> +
+ } + +
diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss index 1ed9f2492..75631d466 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss @@ -4,6 +4,8 @@ @use "styles/typography"; .project { + position: relative; + &__inner { width: 100%; margin-bottom: 25px; @@ -63,6 +65,12 @@ } } } + + &__no-items { + position: absolute; + bottom: 0%; + left: 50%; + } } .invite { @@ -86,7 +94,7 @@ &__team { grid-template-columns: repeat(1, 270px); grid-gap: 20px; - margin-top: 10px; + margin: 15px 0px; @include responsive.apply-desktop { grid-template-columns: repeat(5, 150px); @@ -94,14 +102,20 @@ } &__link { + display: flex; margin-bottom: 12px; + gap: 20px; - &--wrapper { - flex-basis: 50%; + app-button { + flex-basis: 70%; } } } +.error { + color: var(--red) !important; +} + .vacancy { &__submit { display: block; diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.ts b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.ts index 9e0ccbe42..4a17f2b25 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.ts +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.ts @@ -41,6 +41,8 @@ export class ProjectTeamStepComponent implements OnInit { // Константы для селектов readonly rolesMembersList = rolesMembersList; + showFields = false; + ngOnInit(): void { this.projectTeamService.setInvites(this.invites); this.projectTeamService.setCollaborators(this.collaborators); @@ -95,6 +97,13 @@ export class ProjectTeamStepComponent implements OnInit { return this.projectTeamService.inviteFormIsSubmitting; } + /** + * Открытие блоков для создания приглашения + */ + createInvitationBlock(): void { + this.showFields = true; + } + /** * Открытие модального окна приглашения */ diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.html b/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.html index 9fd08d5d2..0c8e787f4 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.html +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.html @@ -2,10 +2,11 @@
- + @if (showFields) {
@if (role; as role) {
+ } @if (description; as description) {
+ @if (requiredExperience; as requiredExperience) {
+ } @if (workFormat; as workFormat) {
+ @if (salary; as salary) {
+ } @if (workSchedule; as workSchedule) {
+
diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.scss index 6df3dedd4..488454f1b 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.scss @@ -4,13 +4,15 @@ @use "styles/typography"; .project { + position: relative; + &__inner { width: 100%; margin-bottom: 25px; @include responsive.apply-desktop { display: flex; - gap: 90px; + gap: 20px; justify-content: space-between; margin-bottom: 0; margin-bottom: 20px; @@ -22,20 +24,12 @@ } &__left { - flex-basis: 50%; + flex-basis: 60%; margin-bottom: 20px; - - form { - width: 280px; - - @include responsive.apply-desktop { - width: 600px; - } - } } &__right { - flex-basis: 50%; + flex-basis: 40%; :first-child & :not(span, fieldset, label, h4, p, i) { margin-top: 26px; @@ -46,6 +40,12 @@ margin-top: 10px; } } + + &__no-items { + position: absolute; + bottom: 0%; + left: 50%; + } } .invite { @@ -56,7 +56,7 @@ .vacancy { &__item { - margin-bottom: 12px; + margin-bottom: 10px; } fieldset { diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.ts b/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.ts index bc29b85c8..037b00cce 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.ts +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.ts @@ -44,6 +44,7 @@ export class ProjectVacancyStepComponent implements OnInit { private readonly route = inject(ActivatedRoute); readonly errorMessage = ErrorMessage; + showFields = false; ngOnInit(): void { this.projectVacancyService.setVacancies(this.vacancies); @@ -131,6 +132,13 @@ export class ProjectVacancyStepComponent implements OnInit { return this.projectVacancyService.vacancyIsSubmitting; } + /** + * + */ + createVacancyBlock(): void { + this.showFields = true; + } + /** * Отправка формы вакансии */ diff --git a/projects/social_platform/src/app/office/projects/list/list.component.scss b/projects/social_platform/src/app/office/projects/list/list.component.scss index 00438d4cb..c6967f2ad 100644 --- a/projects/social_platform/src/app/office/projects/list/list.component.scss +++ b/projects/social_platform/src/app/office/projects/list/list.component.scss @@ -117,7 +117,7 @@ cursor: pointer; background-color: var(--white); border: 1px solid var(--medium-medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); @include responsive.apply-desktop { display: none; diff --git a/projects/social_platform/src/app/office/projects/projects.component.scss b/projects/social_platform/src/app/office/projects/projects.component.scss index 0e893e76a..bf1056f60 100644 --- a/projects/social_platform/src/app/office/projects/projects.component.scss +++ b/projects/social_platform/src/app/office/projects/projects.component.scss @@ -20,7 +20,7 @@ padding: 20px; background-color: var(--white); border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); } .bar__add-project { diff --git a/projects/social_platform/src/app/office/shared/carousel/carousel.component.scss b/projects/social_platform/src/app/office/shared/carousel/carousel.component.scss index 33e8a65b0..162f8bb53 100644 --- a/projects/social_platform/src/app/office/shared/carousel/carousel.component.scss +++ b/projects/social_platform/src/app/office/shared/carousel/carousel.component.scss @@ -15,7 +15,7 @@ img { display: block; width: 100%; - border-radius: 15px; + border-radius: var(--rounded-xl); object-fit: cover; } } @@ -27,7 +27,7 @@ z-index: 10; padding: 2px 20px; background-color: #81818180; - border-radius: 15px; + border-radius: var(--rounded-xl); span { color: var(--black); @@ -69,7 +69,7 @@ height: 75px; color: var(--accent); background-color: var(--white); - border-radius: 15px; + border-radius: var(--rounded-xl); transition: transform 0.1s ease-in-out; transform: translate(-50%, -50%) scale(0); diff --git a/projects/social_platform/src/app/office/shared/img-card/img-card.component.scss b/projects/social_platform/src/app/office/shared/img-card/img-card.component.scss index 32957dc59..3c809c68b 100644 --- a/projects/social_platform/src/app/office/shared/img-card/img-card.component.scss +++ b/projects/social_platform/src/app/office/shared/img-card/img-card.component.scss @@ -5,7 +5,7 @@ justify-content: center; width: 130px; height: 130px; - border-radius: 15px; + border-radius: var(--rounded-xl); &--loading { background-color: var(--dark-grey); @@ -32,7 +32,7 @@ width: 100%; height: 100%; object-fit: cover; - border-radius: 15px; + border-radius: var(--rounded-xl); } } diff --git a/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.html b/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.html index 4342d9b66..9620af87e 100644 --- a/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.html +++ b/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.html @@ -10,29 +10,27 @@ }
- -

- {{ invite.user.firstName }} {{ invite.user.lastName }} -

- -

- {{ invite.role }} -

+
+ +

+ {{ invite.user.firstName }} {{ invite.user.lastName }} +

+
-
- + - - - - -
+
} diff --git a/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.scss b/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.scss index c50e11962..151a11d3c 100644 --- a/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.scss +++ b/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.scss @@ -4,9 +4,10 @@ .invite { display: flex; - align-items: flex-start; + flex-direction: column; justify-content: space-between; padding: 15px 10px; + gap: 15px; background-color: var(--light-gray); border-radius: var(--rounded-md); @@ -28,6 +29,12 @@ gap: 5px; } + &__user { + display: flex; + align-items: center; + gap: 10px; + } + &__top { display: flex; gap: 15px; @@ -93,8 +100,4 @@ &__edit { color: var(--dark-grey); } - - &__cross { - color: var(--red); - } } diff --git a/projects/social_platform/src/app/office/shared/member-card/member-card.component.scss b/projects/social_platform/src/app/office/shared/member-card/member-card.component.scss index 8a390cfd1..f14314bf5 100644 --- a/projects/social_platform/src/app/office/shared/member-card/member-card.component.scss +++ b/projects/social_platform/src/app/office/shared/member-card/member-card.component.scss @@ -7,7 +7,7 @@ padding: 20px; background-color: var(--white); border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); &:hover { box-shadow: 0 0 6px var(--gray); diff --git a/projects/social_platform/src/app/office/shared/news-card/news-card.component.scss b/projects/social_platform/src/app/office/shared/news-card/news-card.component.scss index bdf20767b..ca9245771 100644 --- a/projects/social_platform/src/app/office/shared/news-card/news-card.component.scss +++ b/projects/social_platform/src/app/office/shared/news-card/news-card.component.scss @@ -5,7 +5,7 @@ padding: 20px; background-color: var(--white); border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); &__head { display: flex; @@ -34,7 +34,7 @@ padding: 20px 0; background-color: var(--white); border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); } &__option { @@ -148,7 +148,7 @@ height: 75px; color: var(--accent); background-color: var(--white); - border-radius: 15px; + border-radius: var(--rounded-xl); transition: transform 0.1s ease-in-out; transform: translate(-50%, -50%) scale(0); diff --git a/projects/social_platform/src/app/office/shared/news-form/news-form.component.scss b/projects/social_platform/src/app/office/shared/news-form/news-form.component.scss index c3eca6a71..64b894d34 100644 --- a/projects/social_platform/src/app/office/shared/news-form/news-form.component.scss +++ b/projects/social_platform/src/app/office/shared/news-form/news-form.component.scss @@ -2,7 +2,7 @@ padding: 16px 20px; background-color: var(--white); border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); &__row { display: flex; diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss b/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss index 7fb742dda..1fcc12526 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss +++ b/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss @@ -13,7 +13,7 @@ height: 200px; padding: 24px 15px; background-color: var(--white); - border-radius: 15px; + border-radius: var(--rounded-xl); @include responsive.apply-desktop { border: 1px solid var(--medium-grey-for-outline); diff --git a/projects/social_platform/src/app/office/shared/skills-basket/skills-basket.component.html b/projects/social_platform/src/app/office/shared/skills-basket/skills-basket.component.html index 025a1879b..6020c3638 100644 --- a/projects/social_platform/src/app/office/shared/skills-basket/skills-basket.component.html +++ b/projects/social_platform/src/app/office/shared/skills-basket/skills-basket.component.html @@ -3,8 +3,8 @@
@for (skill of value(); track skill.id) { -
- {{ skill.name }} +
+ {{ skill.name }} -
-

{{ vacancy.role }}

- @if (vacancy.requiredSkills.length) { -

- {{ skillString }} {{ vacancy.specialization ? "•" : "" }} - {{ vacancy.specialization ? vacancy.specialization : "" }} -

- } +
+
+

{{ vacancy.role }}

+
+
+ @if (vacancy.requiredSkills.length) { @for (skill of vacancy.requiredSkills; track $index) { +

+ {{ skill.name }} +

+ + @if (vacancy.specialization) { +

+ {{ vacancy.specialization ? vacancy.specialization : "" }} +

+ } } } +
+
- - + + + + + + +
} diff --git a/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.scss b/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.scss index ea0653c23..1631994df 100644 --- a/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.scss +++ b/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.scss @@ -1,39 +1,54 @@ /** @format */ .vacancy { - display: flex; - align-items: center; - justify-content: space-between; - padding: 10px; - background-color: var(--light-gray); - border-radius: var(--rounded-md); - - &__role { - color: var(--black); + &__requirements { + color: var(--accent); + padding: 5px 40px; + background: transparent; + border: 1px solid var(--accent); + border-radius: var(--rounded-xxl); + + &--soft { + color: var(--white); + background-color: var(--accent); + border: none; + } } - &__requirements { - color: var(--dark-grey); + &__info { + display: flex; + flex-direction: column; + + &--text { + border-bottom: 1px solid var(--dark-grey); + padding-bottom: 5px; + margin-bottom: 10px; + } } - &__icons { + &__skills { display: flex; - gap: 15px; - align-items: center; - margin-left: 15px; + flex-direction: row; + flex-wrap: wrap; + gap: 10px; } - &__basket { - color: var(--red); - cursor: pointer; + &__icons { + display: flex; + gap: 20px; + margin-top: 10px; + + app-button { + flex-grow: 1; + } } &__edit { - color: var(--dark-grey); - cursor: pointer; + margin-top: 8px; } i { + cursor: pointer; width: 24px; height: 24px; } diff --git a/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.ts b/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.ts index c787fef05..aa8cea837 100644 --- a/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.ts +++ b/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.ts @@ -2,7 +2,7 @@ import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { Vacancy } from "@models/vacancy.model"; -import { IconComponent } from "@ui/components"; +import { IconComponent, ButtonComponent } from "@ui/components"; /** * Компонент карточки вакансии @@ -29,7 +29,7 @@ import { IconComponent } from "@ui/components"; templateUrl: "./vacancy-card.component.html", styleUrl: "./vacancy-card.component.scss", standalone: true, - imports: [IconComponent], + imports: [IconComponent, ButtonComponent], }) export class VacancyCardComponent implements OnInit { constructor() {} @@ -40,10 +40,7 @@ export class VacancyCardComponent implements OnInit { skillString = ""; - ngOnInit(): void { - // Формирование строки навыков с разделителем - this.skillString = this.vacancy?.requiredSkills.map(s => s.name).join(" • ") ?? ""; - } + ngOnInit(): void {} /** * Обработчик удаления вакансии diff --git a/projects/social_platform/src/app/office/vacancies/detail/info/info.component.scss b/projects/social_platform/src/app/office/vacancies/detail/info/info.component.scss index b32876acd..b3368f19d 100644 --- a/projects/social_platform/src/app/office/vacancies/detail/info/info.component.scss +++ b/projects/social_platform/src/app/office/vacancies/detail/info/info.component.scss @@ -36,7 +36,7 @@ position: relative; height: 230px; margin-bottom: 20px; - border-radius: 15px; + border-radius: var(--rounded-xl); img { position: absolute; @@ -78,7 +78,7 @@ text-align: center; background-color: var(--white); border: 1px solid var(--gray); - border-radius: 15px; + border-radius: var(--rounded-xl); @include typography.body-14; } @@ -88,7 +88,7 @@ margin: 20px 0; background-color: var(--white); border: 1px solid var(--gray); - border-radius: 15px; + border-radius: var(--rounded-xl); } &__title { @@ -131,7 +131,7 @@ ::ng-deep .button { &.button--inline, &.button--outline { - border-radius: 15px; + border-radius: var(--rounded-xl); } } } @@ -148,7 +148,7 @@ cursor: pointer; border: 1px solid; border-color: var(--gradient); - border-radius: 15px; + border-radius: var(--rounded-xl); } &__project-image { diff --git a/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.scss b/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.scss index 028408232..4956aa35f 100644 --- a/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.scss +++ b/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.scss @@ -14,7 +14,7 @@ padding: 20px; background-color: var(--white); border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); @include responsive.apply-desktop { display: flex; diff --git a/projects/social_platform/src/app/office/vacancy/send/send.component.html b/projects/social_platform/src/app/office/vacancy/send/send.component.html index a36b757a6..2a6b364bf 100644 --- a/projects/social_platform/src/app/office/vacancy/send/send.component.html +++ b/projects/social_platform/src/app/office/vacancy/send/send.component.html @@ -96,12 +96,12 @@

>
- -

Прикрепить резюме

+ +

Прикрепить резюме

Резюме формата .pdf, .docx

@if (accompanyingFile | controlError: "required") { -

Загрузите файл

+

Загрузите файл

}
diff --git a/projects/social_platform/src/app/office/vacancy/send/send.component.scss b/projects/social_platform/src/app/office/vacancy/send/send.component.scss index 75397277d..bea2c4205 100644 --- a/projects/social_platform/src/app/office/vacancy/send/send.component.scss +++ b/projects/social_platform/src/app/office/vacancy/send/send.component.scss @@ -79,7 +79,7 @@ margin-top: 15px; margin-bottom: 60px; border: 1px solid var(--gray); - border-radius: 15px; + border-radius: var(--rounded-xl); &__list { margin-top: 24px; diff --git a/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.html b/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.html index 14bd2607e..c3be7a55e 100644 --- a/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.html +++ b/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.html @@ -33,7 +33,7 @@
}
- +

diff --git a/projects/social_platform/src/app/ui/components/bar/bar.component.scss b/projects/social_platform/src/app/ui/components/bar/bar.component.scss index c321bd5bc..e2b4c3190 100644 --- a/projects/social_platform/src/app/ui/components/bar/bar.component.scss +++ b/projects/social_platform/src/app/ui/components/bar/bar.component.scss @@ -7,7 +7,7 @@ overflow: hidden; background-color: var(--white); border: 1px solid var(--medium-grey-for-outline); - border-radius: 15px; + border-radius: var(--rounded-xl); &__nav { display: flex; diff --git a/projects/social_platform/src/app/ui/components/button/button.component.scss b/projects/social_platform/src/app/ui/components/button/button.component.scss index 5580a4cb2..c94ce4b6b 100644 --- a/projects/social_platform/src/app/ui/components/button/button.component.scss +++ b/projects/social_platform/src/app/ui/components/button/button.component.scss @@ -12,7 +12,7 @@ cursor: pointer; background: var(--accent); border: 2px solid transparent; - border-radius: var(--rounded-xl); + border-radius: var(--rounded-xxl); outline: none; transition: background-color 0.2s; @@ -93,7 +93,7 @@ cursor: pointer; background-color: transparent; border: 1px solid var(--accent); - border-radius: var(--rounded-xl); + border-radius: var(--rounded-xxl); transition: all 0.2s; &:hover { @@ -128,5 +128,13 @@ &.button--no-border { border: none; } + + ::ng-deep *:not(.dot-wave) { + display: block; + + &:not(:last-child) { + margin-right: 10px; + } + } } } diff --git a/projects/social_platform/src/app/ui/components/input/input.component.html b/projects/social_platform/src/app/ui/components/input/input.component.html index 4f5b20fd9..8b871bcca 100644 --- a/projects/social_platform/src/app/ui/components/input/input.component.html +++ b/projects/social_platform/src/app/ui/components/input/input.component.html @@ -9,11 +9,15 @@ (input)="onInput($event)" (keydown.enter)="onEnter($event)" (blur)="onBlur()" + (focus)="onFocus()" [placeholder]="placeholder" - [type]="type" + [type]="currentType" [mask]="mask" [dropSpecialCharacters]="true" class="field__input" + [class.field__input--small]="size === 'small'" + [class.field__input--medium]="size === 'medium'" + [class.field__input--big]="size === 'big'" [class.field__input--error]="error" /> @if (error) { diff --git a/projects/social_platform/src/app/ui/components/input/input.component.scss b/projects/social_platform/src/app/ui/components/input/input.component.scss index 7a7c613bd..7f68d7a99 100644 --- a/projects/social_platform/src/app/ui/components/input/input.component.scss +++ b/projects/social_platform/src/app/ui/components/input/input.component.scss @@ -7,12 +7,10 @@ position: relative; &__input { - width: 100%; - padding: 13px 20px; - color: var(--black); + color: var(--black) !important; background-color: var(--white); border: 1px solid var(--gray); - border-radius: var(--rounded-xl); + border-radius: var(--rounded-xxl); outline: none; transition: all 0.2s; @@ -20,6 +18,17 @@ color: var(--dark-grey); } + &--small { + padding: 12px; + max-width: 70px; + text-align: center; + } + + &--big { + width: 100%; + padding: 12px 105px 12px 12px; + } + &--error { border-color: var(--red); } @@ -29,10 +38,16 @@ box-shadow: 0 0 6px rgb(109 40 255 / 30%); } - @include typography.body-14; + @include typography.body-10; @include responsive.apply-desktop { - @include typography.body-16; + @include typography.body-12; + } + + input[type="date"]::-webkit-inner-spin-button, + input[type="date"]::-webkit-calendar-picker-indicator { + display: none; + -webkit-appearance: none; } } diff --git a/projects/social_platform/src/app/ui/components/input/input.component.ts b/projects/social_platform/src/app/ui/components/input/input.component.ts index 14ccf55c0..19e05f224 100644 --- a/projects/social_platform/src/app/ui/components/input/input.component.ts +++ b/projects/social_platform/src/app/ui/components/input/input.component.ts @@ -47,6 +47,9 @@ export class InputComponent implements OnInit, ControlValueAccessor { /** Тип поля ввода */ @Input() type: "text" | "password" | "email" | "tel" | "date" = "text"; + /** Размер поля ввода */ + @Input() size: "small" | "medium" | "big" = "small"; + /** Состояние ошибки */ @Input() error = false; @@ -63,13 +66,22 @@ export class InputComponent implements OnInit, ControlValueAccessor { return this.value; } + /** Изначальный тип поля (для восстановления после blur) */ + private originalType: "text" | "password" | "email" | "tel" | "date" = "text"; + + /** Текущий активный тип поля */ + currentType: "text" | "password" | "email" | "tel" | "date" = "text"; + /** Событие изменения значения */ @Output() appValueChange = new EventEmitter(); /** Событие нажатия Enter */ @Output() enter = new EventEmitter(); - ngOnInit(): void {} + ngOnInit(): void { + this.originalType = this.type; + this.currentType = this.type === "date" ? "text" : this.type; + } /** Обработчик ввода текста */ onInput(event: Event): void { @@ -80,9 +92,19 @@ export class InputComponent implements OnInit, ControlValueAccessor { /** Обработчик потери фокуса */ onBlur(): void { + if (this.originalType === "date") { + this.currentType = "text"; + } this.onTouch(); } + /** Обработчик при фокусе */ + onFocus(): void { + if (this.originalType === "date") { + this.currentType = "date"; + } + } + /** Текущее значение поля */ value = ""; diff --git a/projects/social_platform/src/app/ui/components/modal/modal.component.scss b/projects/social_platform/src/app/ui/components/modal/modal.component.scss index d93f8c881..e37d9310e 100644 --- a/projects/social_platform/src/app/ui/components/modal/modal.component.scss +++ b/projects/social_platform/src/app/ui/components/modal/modal.component.scss @@ -29,7 +29,7 @@ padding: 24px; overflow: hidden; background: var(--white); - border-radius: 15px; + border-radius: var(--rounded-xl); transform: translate(-50%, -50%); @include responsive.apply-desktop { diff --git a/projects/social_platform/src/app/ui/components/select/select.component.html b/projects/social_platform/src/app/ui/components/select/select.component.html index eda6080fd..fe3bd0115 100644 --- a/projects/social_platform/src/app/ui/components/select/select.component.html +++ b/projects/social_platform/src/app/ui/components/select/select.component.html @@ -2,9 +2,11 @@
{{ @@ -27,11 +29,12 @@
    @for (option of options; track option.id; let index = $index) {
  • {{ option.label }} +
  • }
diff --git a/projects/social_platform/src/app/ui/components/select/select.component.scss b/projects/social_platform/src/app/ui/components/select/select.component.scss index b9d248b40..aa02ec29b 100644 --- a/projects/social_platform/src/app/ui/components/select/select.component.scss +++ b/projects/social_platform/src/app/ui/components/select/select.component.scss @@ -13,15 +13,24 @@ display: flex; align-items: center; justify-content: space-between; - width: 100%; - padding: 12px 20px; color: var(--black); cursor: pointer; background-color: var(--white); border: 1px solid var(--gray); - border-radius: var(--rounded-xl); + border-radius: var(--rounded-xxl); outline: none; + &--small { + max-width: 70px; + padding: 12px; + text-align: center; + } + + &--big { + width: 100%; + padding: 12px; + } + &--placeholder { color: var(--dark-grey); } @@ -31,21 +40,17 @@ box-shadow: 0 0 6px rgb(109 40 255 / 30%); } - @include typography.body-14; - - @include responsive.apply-desktop { - @include typography.body-16; - } + @include typography.body-12; } &__options { position: absolute; right: 0; bottom: -6px; - left: 0; + left: 5%; z-index: 11; + width: 90%; max-height: 200px; - padding: 10px; overflow-y: auto; background-color: var(--white); border: 1px solid var(--gray); @@ -54,7 +59,10 @@ } &__option { - padding: 18px; + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 18px; color: var(--dark-grey); cursor: pointer; border-radius: var(--rounded-lg); @@ -65,14 +73,11 @@ } &:hover { - color: var(--white); - background-color: var(--accent); + color: var(--accent); } - @include typography.body-14; - - @include responsive.apply-desktop { - @include typography.body-16; + &--point { + color: var(--accent); } } diff --git a/projects/social_platform/src/app/ui/components/select/select.component.ts b/projects/social_platform/src/app/ui/components/select/select.component.ts index 5aea526b3..adf937b09 100644 --- a/projects/social_platform/src/app/ui/components/select/select.component.ts +++ b/projects/social_platform/src/app/ui/components/select/select.component.ts @@ -53,6 +53,8 @@ export class SelectComponent implements ControlValueAccessor { /** ID выбранной опции */ @Input() selectedId?: number; + @Input() size: "small" | "big" = "small"; + /** Массив доступных опций */ @Input({ required: true }) options: { value: string | number; diff --git a/projects/social_platform/src/app/ui/components/textarea/textarea.component.html b/projects/social_platform/src/app/ui/components/textarea/textarea.component.html index d06f2caf4..83bf52e9b 100644 --- a/projects/social_platform/src/app/ui/components/textarea/textarea.component.html +++ b/projects/social_platform/src/app/ui/components/textarea/textarea.component.html @@ -11,7 +11,10 @@ (blur)="onBlur()" [disabled]="disabled" [placeholder]="placeholder" - class="text-body-16 field__input" + class="text-body-12 field__input" + [class.field__input--small]="size === 'small'" + [class.field__input--medium]="size === 'medium'" + [class.field__input--big]="size === 'big'" [class.field__input--error]="error" >{{ value }} diff --git a/projects/social_platform/src/app/ui/components/textarea/textarea.component.scss b/projects/social_platform/src/app/ui/components/textarea/textarea.component.scss index 619adb170..8dfff7dcd 100644 --- a/projects/social_platform/src/app/ui/components/textarea/textarea.component.scss +++ b/projects/social_platform/src/app/ui/components/textarea/textarea.component.scss @@ -7,20 +7,29 @@ position: relative; &__input { - width: 100%; - padding: 12px 20px; + color: var(--black); resize: none; background-color: var(--white); border: 1px solid var(--gray); - border-radius: var(--rounded-lg); + border-radius: var(--rounded-xl); outline: none; + min-height: 162px; + padding: 12px 18px; transition: all 0.2s; &::placeholder { color: var(--dark-grey); } + &--small { + min-width: 245px; + } + + &--big { + width: 100%; + } + &--error { border-color: var(--red); } @@ -29,12 +38,6 @@ border-color: var(--accent); box-shadow: 0 0 6px rgb(109 40 255 / 30%); } - - @include typography.body-14; - - @include responsive.apply-desktop { - @include typography.body-16; - } } &__error-icon { diff --git a/projects/social_platform/src/app/ui/components/textarea/textarea.component.ts b/projects/social_platform/src/app/ui/components/textarea/textarea.component.ts index daf85ef94..240fb91a7 100644 --- a/projects/social_platform/src/app/ui/components/textarea/textarea.component.ts +++ b/projects/social_platform/src/app/ui/components/textarea/textarea.component.ts @@ -46,6 +46,8 @@ export class TextareaComponent implements OnInit, ControlValueAccessor { /** Состояние ошибки */ @Input() error = false; + @Input() size: "small" | "medium" | "big" = "small"; + /** Маска (наследуется, но не используется) */ @Input() mask = ""; diff --git a/projects/social_platform/src/app/ui/components/upload-file/upload-file.component.html b/projects/social_platform/src/app/ui/components/upload-file/upload-file.component.html index bc9fef56a..fe6a03023 100644 --- a/projects/social_platform/src/app/ui/components/upload-file/upload-file.component.html +++ b/projects/social_platform/src/app/ui/components/upload-file/upload-file.component.html @@ -1,8 +1,8 @@ -
+
@if (loading) { -

Загрузка...

+ } @else if (!value) {
@@ -13,9 +13,12 @@
} @else { -
-

{{ value | slice: 0:70 }}

- +
+
+ +

Файл успешно загружен

+
+
}
diff --git a/projects/social_platform/src/app/ui/components/upload-file/upload-file.component.scss b/projects/social_platform/src/app/ui/components/upload-file/upload-file.component.scss index 4fda2f466..152bcdfd3 100644 --- a/projects/social_platform/src/app/ui/components/upload-file/upload-file.component.scss +++ b/projects/social_platform/src/app/ui/components/upload-file/upload-file.component.scss @@ -1,9 +1,14 @@ /** @format */ .control { - padding: 26px; - border: 2px dashed var(--accent); + padding: 18px 30px; + height: 120px; border-radius: var(--rounded-md); + border: 1px solid var(--dark-grey); + display: flex; + flex-direction: column; + align-items: center; + cursor: pointer; &__empty { display: flex; @@ -12,15 +17,28 @@ cursor: pointer; } + &__success { + display: flex; + flex: 1; + align-self: stretch; + align-items: center; + justify-content: center; + gap: 10px; + } + &--error { border: 2px solid var(--red); } + + &:hover { + border: 1px solid var(--gray); + } } .file { display: flex; align-items: center; - justify-content: space-between; + gap: 10px; padding: 10px; background-color: var(--light-gray); border-radius: var(--rounded-lg); @@ -37,6 +55,7 @@ color: var(--red); cursor: pointer; transition: color 0.2s; + align-self: flex-end; &:hover { color: var(--red-dark); diff --git a/projects/social_platform/src/app/ui/components/upload-file/upload-file.component.ts b/projects/social_platform/src/app/ui/components/upload-file/upload-file.component.ts index cc34f319f..d358c2a07 100644 --- a/projects/social_platform/src/app/ui/components/upload-file/upload-file.component.ts +++ b/projects/social_platform/src/app/ui/components/upload-file/upload-file.component.ts @@ -6,6 +6,7 @@ import { FileService } from "@core/services/file.service"; import { nanoid } from "nanoid"; import { IconComponent } from "@ui/components"; import { SlicePipe } from "@angular/common"; +import { LoaderComponent } from "../loader/loader.component"; /** * Компонент для загрузки файлов с предварительным просмотром. @@ -37,7 +38,7 @@ import { SlicePipe } from "@angular/common"; }, ], standalone: true, - imports: [IconComponent, SlicePipe], + imports: [IconComponent, SlicePipe, LoaderComponent], }) export class UploadFileComponent implements OnInit, ControlValueAccessor { constructor(private fileService: FileService) {} diff --git a/projects/social_platform/src/assets/icons/svg/edit-pen-old.svg b/projects/social_platform/src/assets/icons/svg/edit-pen-old.svg new file mode 100644 index 000000000..90723d83f --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/edit-pen-old.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/svg/file-success.svg b/projects/social_platform/src/assets/icons/svg/file-success.svg new file mode 100644 index 000000000..912f7364d --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/file-success.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/svg/file.svg b/projects/social_platform/src/assets/icons/svg/file.svg index 77a82b40a..0b6e71797 100644 --- a/projects/social_platform/src/assets/icons/svg/file.svg +++ b/projects/social_platform/src/assets/icons/svg/file.svg @@ -1,6 +1,3 @@ - - - - - + + diff --git a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg index 39e6f032e..61d7f4498 100644 --- a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg +++ b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/projects/social_platform/src/styles/_rounded.scss b/projects/social_platform/src/styles/_rounded.scss index 91203f86a..b2602fac5 100644 --- a/projects/social_platform/src/styles/_rounded.scss +++ b/projects/social_platform/src/styles/_rounded.scss @@ -4,5 +4,6 @@ --rounded-sm: 3px; --rounded-md: 5px; --rounded-lg: 8px; - --rounded-xl: 45px; + --rounded-xl: 15px; + --rounded-xxl: 45px; } diff --git a/projects/social_platform/src/styles/pages/_members.scss b/projects/social_platform/src/styles/pages/_members.scss index c52de76da..15abbaa54 100644 --- a/projects/social_platform/src/styles/pages/_members.scss +++ b/projects/social_platform/src/styles/pages/_members.scss @@ -9,7 +9,7 @@ padding: 20px; margin-bottom: 12px; background-color: var(--white); - border-radius: 15px; + border-radius: var(--rounded-xl); } &__list { From 1f1cc1ae3d5240e3395b152f99ba8b206d1bf8d8 Mon Sep 17 00:00:00 2001 From: Awakich Date: Fri, 5 Sep 2025 22:14:26 +0300 Subject: [PATCH 030/126] add styles for main stage, contacts & upload-file, textarea, input, select components --- .../app/office/projects/edit/edit.component.scss | 3 +-- .../project-main-step.component.scss | 7 ++++--- .../project-navigation.component.scss | 4 ++-- .../project-team-step.component.scss | 4 ++-- .../invite-card/invite-card.component.scss | 4 ++-- .../vacancy-card/vacancy-card.component.scss | 9 ++++----- .../vacancies/detail/info/info.component.scss | 2 +- .../app/ui/components/input/input.component.scss | 6 +++--- .../ui/components/select/select.component.scss | 2 +- .../components/textarea/textarea.component.scss | 5 ++--- .../upload-file/upload-file.component.scss | 16 ++++++++-------- 11 files changed, 30 insertions(+), 32 deletions(-) diff --git a/projects/social_platform/src/app/office/projects/edit/edit.component.scss b/projects/social_platform/src/app/office/projects/edit/edit.component.scss index cc47a2311..846db3cd5 100644 --- a/projects/social_platform/src/app/office/projects/edit/edit.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/edit.component.scss @@ -5,8 +5,7 @@ .project { position: relative; - - padding: 80px 0px; + padding: 80px 0; background-color: var(--white); border-radius: var(--rounded-md); diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.scss index 943b5aa4f..7dd3c3194 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.scss @@ -10,8 +10,8 @@ &__grid { display: grid; - align-items: center; grid-gap: 20px; + align-items: center; margin-bottom: 20px; } @@ -28,8 +28,8 @@ &--additional { display: flex; - align-items: center; gap: 20px; + align-items: center; fieldset { flex-basis: 50%; @@ -37,7 +37,8 @@ } } - &__problem, &__description { + &__problem, + &__description { margin-bottom: 12px; } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.scss index 1fd44ba67..375ad6d34 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.scss @@ -16,9 +16,9 @@ gap: 10px; align-items: center; justify-content: center; + padding: 2px; background-color: var(--gray); border-radius: var(--rounded-xxl); - padding: 2px; @include responsive.apply-desktop { gap: 0; @@ -33,8 +33,8 @@ cursor: pointer; &--active { + padding: 0 10px; background-color: var(--white); - padding: 0px 10px; border-radius: var(--rounded-xxl); } } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss index 75631d466..e3466f8d7 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss @@ -94,7 +94,7 @@ &__team { grid-template-columns: repeat(1, 270px); grid-gap: 20px; - margin: 15px 0px; + margin: 15px 0; @include responsive.apply-desktop { grid-template-columns: repeat(5, 150px); @@ -103,8 +103,8 @@ &__link { display: flex; - margin-bottom: 12px; gap: 20px; + margin-bottom: 12px; app-button { flex-basis: 70%; diff --git a/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.scss b/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.scss index 151a11d3c..ad206af7e 100644 --- a/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.scss +++ b/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.scss @@ -5,9 +5,9 @@ .invite { display: flex; flex-direction: column; + gap: 15px; justify-content: space-between; padding: 15px 10px; - gap: 15px; background-color: var(--light-gray); border-radius: var(--rounded-md); @@ -31,8 +31,8 @@ &__user { display: flex; - align-items: center; gap: 10px; + align-items: center; } &__top { diff --git a/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.scss b/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.scss index 1631994df..a5cab82ee 100644 --- a/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.scss +++ b/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.scss @@ -2,8 +2,8 @@ .vacancy { &__requirements { - color: var(--accent); padding: 5px 40px; + color: var(--accent); background: transparent; border: 1px solid var(--accent); border-radius: var(--rounded-xxl); @@ -20,16 +20,15 @@ flex-direction: column; &--text { - border-bottom: 1px solid var(--dark-grey); padding-bottom: 5px; margin-bottom: 10px; + border-bottom: 1px solid var(--dark-grey); } } &__skills { display: flex; - flex-direction: row; - flex-wrap: wrap; + flex-flow: row wrap; gap: 10px; } @@ -48,8 +47,8 @@ } i { - cursor: pointer; width: 24px; height: 24px; + cursor: pointer; } } diff --git a/projects/social_platform/src/app/office/vacancies/detail/info/info.component.scss b/projects/social_platform/src/app/office/vacancies/detail/info/info.component.scss index b3368f19d..acf96401a 100644 --- a/projects/social_platform/src/app/office/vacancies/detail/info/info.component.scss +++ b/projects/social_platform/src/app/office/vacancies/detail/info/info.component.scss @@ -148,7 +148,7 @@ cursor: pointer; border: 1px solid; border-color: var(--gradient); - border-radius: var(--rounded-xl); + border-radius: var(--rounded-xl); } &__project-image { diff --git a/projects/social_platform/src/app/ui/components/input/input.component.scss b/projects/social_platform/src/app/ui/components/input/input.component.scss index 7f68d7a99..29ea5b17e 100644 --- a/projects/social_platform/src/app/ui/components/input/input.component.scss +++ b/projects/social_platform/src/app/ui/components/input/input.component.scss @@ -19,8 +19,8 @@ } &--small { - padding: 12px; max-width: 70px; + padding: 12px; text-align: center; } @@ -46,8 +46,8 @@ input[type="date"]::-webkit-inner-spin-button, input[type="date"]::-webkit-calendar-picker-indicator { - display: none; - -webkit-appearance: none; + display: none; + appearance: none; } } diff --git a/projects/social_platform/src/app/ui/components/select/select.component.scss b/projects/social_platform/src/app/ui/components/select/select.component.scss index aa02ec29b..321ef9311 100644 --- a/projects/social_platform/src/app/ui/components/select/select.component.scss +++ b/projects/social_platform/src/app/ui/components/select/select.component.scss @@ -60,8 +60,8 @@ &__option { display: flex; - justify-content: space-between; align-items: center; + justify-content: space-between; padding: 12px 18px; color: var(--dark-grey); cursor: pointer; diff --git a/projects/social_platform/src/app/ui/components/textarea/textarea.component.scss b/projects/social_platform/src/app/ui/components/textarea/textarea.component.scss index 8dfff7dcd..747f800fd 100644 --- a/projects/social_platform/src/app/ui/components/textarea/textarea.component.scss +++ b/projects/social_platform/src/app/ui/components/textarea/textarea.component.scss @@ -7,15 +7,14 @@ position: relative; &__input { - + min-height: 162px; + padding: 12px 18px; color: var(--black); resize: none; background-color: var(--white); border: 1px solid var(--gray); border-radius: var(--rounded-xl); outline: none; - min-height: 162px; - padding: 12px 18px; transition: all 0.2s; &::placeholder { diff --git a/projects/social_platform/src/app/ui/components/upload-file/upload-file.component.scss b/projects/social_platform/src/app/ui/components/upload-file/upload-file.component.scss index 152bcdfd3..21ca1f327 100644 --- a/projects/social_platform/src/app/ui/components/upload-file/upload-file.component.scss +++ b/projects/social_platform/src/app/ui/components/upload-file/upload-file.component.scss @@ -1,14 +1,14 @@ /** @format */ .control { - padding: 18px 30px; - height: 120px; - border-radius: var(--rounded-md); - border: 1px solid var(--dark-grey); display: flex; flex-direction: column; align-items: center; + height: 120px; + padding: 18px 30px; cursor: pointer; + border: 1px solid var(--dark-grey); + border-radius: var(--rounded-md); &__empty { display: flex; @@ -20,10 +20,10 @@ &__success { display: flex; flex: 1; - align-self: stretch; + gap: 10px; align-items: center; + align-self: stretch; justify-content: center; - gap: 10px; } &--error { @@ -37,8 +37,8 @@ .file { display: flex; - align-items: center; gap: 10px; + align-items: center; padding: 10px; background-color: var(--light-gray); border-radius: var(--rounded-lg); @@ -52,10 +52,10 @@ } &__basket { + align-self: flex-end; color: var(--red); cursor: pointer; transition: color 0.2s; - align-self: flex-end; &:hover { color: var(--red-dark); From e80e26eacfd65d0a3259c4232897770ee8cb80f0 Mon Sep 17 00:00:00 2001 From: Awakich Date: Sat, 6 Sep 2025 22:06:13 +0300 Subject: [PATCH 031/126] fix design of textarea, input, select & add error visuality --- .../src/app/auth/login/login.component.html | 10 +- .../app/auth/register/register.component.html | 56 +++++---- .../reset-password.component.html | 13 +- .../set-password/set-password.component.html | 56 +++++---- .../src/app/error/models/error-message.ts | 2 + .../stage-one/stage-one.component.html | 2 +- .../stage-zero/stage-zero.component.html | 86 ++++++++----- .../office/profile/edit/edit.component.html | 117 ++++++++++++------ .../projects-filter.component.html | 1 + .../detail/register/register.component.html | 1 + .../rating-card/rating-card.component.html | 2 +- .../project-achievement-step.component.html | 4 +- .../project-additional-step.component.html | 5 +- .../project-additional-step.component.scss | 10 +- .../project-contacts-step.component.html | 2 +- .../project-contacts-step.component.scss | 4 + .../project-main-step.component.html | 20 +-- .../project-main-step.component.scss | 4 + .../project-team-step.component.html | 12 +- .../project-team-step.component.scss | 2 +- .../project-vacancy-step.component.html | 38 +++--- .../project-vacancy-step.component.scss | 22 +--- .../project-vacancy-step.component.ts | 2 + .../invite-card/invite-card.component.html | 6 +- .../shared/news-card/news-card.component.html | 2 +- .../project-rating.component.html | 1 + .../project-rating.component.scss | 2 +- .../vacancy-card/vacancy-card.component.html | 9 +- .../vacancy-card/vacancy-card.component.scss | 5 + .../filter/vacancy-filter.component.html | 2 + .../filter/vacancy-filter.component.scss | 2 +- .../office/vacancy/send/send.component.html | 7 +- .../autocomplete-input.component.html | 10 +- .../autocomplete-input.component.scss | 16 ++- .../autocomplete-input.component.ts | 2 + .../ui/components/input/input.component.html | 13 +- .../ui/components/input/input.component.ts | 39 +++++- .../components/select/select.component.html | 5 + .../components/select/select.component.scss | 4 + .../ui/components/select/select.component.ts | 5 +- .../textarea/textarea.component.html | 4 +- .../textarea/textarea.component.scss | 4 +- .../components/textarea/textarea.component.ts | 5 +- .../src/assets/icons/svg/box.svg | 3 + .../src/assets/icons/svg/edit-pen.svg | 4 +- .../assets/icons/symbol/svg/sprite.css.svg | 2 +- 46 files changed, 405 insertions(+), 218 deletions(-) create mode 100644 projects/social_platform/src/assets/icons/svg/box.svg 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 e6e5052a7..d7916f0a0 100644 --- a/projects/social_platform/src/app/auth/login/login.component.html +++ b/projects/social_platform/src/app/auth/login/login.component.html @@ -8,6 +8,7 @@

Вход

Вход placeholder="Введите почту" > @if (email | controlError: "email") { -
+
{{ errorMessage.VALIDATION_EMAIL }}
} @if (email | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -31,6 +32,7 @@

Вход

Вход } @if (password | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
}
} @if (errorWrongAuth) { -
+
{{ errorMessage.AUTH_WRONG_AUTH }}
} 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 a6c878c0c..aff9c2b9f 100644 --- a/projects/social_platform/src/app/auth/register/register.component.html +++ b/projects/social_platform/src/app/auth/register/register.component.html @@ -32,6 +32,7 @@

@if (credsSubmitInitiated) { @if (email | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @if (email | controlError: "email") { -
+
{{ errorMessage.VALIDATION_EMAIL }}
} @@ -56,6 +57,7 @@

} @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)
+
Пароль должен содержать минимум одну цифру (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 }}
} @@ -170,6 +173,7 @@

@if (infoSubmitInitiated) { @if (name | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @if (name | controlError: "invalidLanguage") { -
+
{{ errorMessage.VALIDATION_LANGUAGE }}
} @@ -194,6 +198,7 @@

@if (infoSubmitInitiated) { @if (surname | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @if (surname | controlError: "invalidLanguage") { -
+
{{ errorMessage.VALIDATION_LANGUAGE }}
} @@ -220,6 +225,7 @@

@if (infoSubmitInitiated) { @if (birthday | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @if (birthday | controlError: "tooYoung") { -
+
@if (birthday.errors) { {{ errorMessage.MINIMAL_AGE }} {{ birthday.errors["tooYoung"]["requiredAge"] }} лет }
} @if (birthday | controlError: "tooOld") { -
+
@if (birthday.errors) { {{ errorMessage.MAXIMAL_AGE }} {{ birthday.errors["tooOld"]["requiredAge"] }} лет }
} @if (birthday | controlError: "invalidDateFormat") { -
+
{{ errorMessage.INVALID_DATE }}
} @@ -256,7 +262,7 @@

} } @if (serverErrors) { -
+
@for (e of serverErrors; track $index) {

{{ e }}

} @@ -292,7 +298,13 @@

Далее } @else if (step === "info") { - + Создать аккаунт } diff --git a/projects/social_platform/src/app/auth/reset-password/reset-password.component.html b/projects/social_platform/src/app/auth/reset-password/reset-password.component.html index 07d86caa6..2ea805a9c 100644 --- a/projects/social_platform/src/app/auth/reset-password/reset-password.component.html +++ b/projects/social_platform/src/app/auth/reset-password/reset-password.component.html @@ -3,10 +3,11 @@

Забыли пароль?

-

Чтобы сбросить пароль, введите свой электронный адрес.

+

Чтобы сбросить пароль, введите свой электронный адрес.

@if (resetForm.get("email"); as email) {
Забыли пароль?

placeholder="Введите почту" > @if (email | controlError: "email") { -
+
{{ errorMessage.VALIDATION_EMAIL }}
} @if (email | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @if (errorServer) { -
Аккаунт с таким email не зарегистрирован
+
Аккаунт с таким email не зарегистрирован
}

} Отправить diff --git a/projects/social_platform/src/app/auth/set-password/set-password.component.html b/projects/social_platform/src/app/auth/set-password/set-password.component.html index 2b1374465..fcacf7d15 100644 --- a/projects/social_platform/src/app/auth/set-password/set-password.component.html +++ b/projects/social_platform/src/app/auth/set-password/set-password.component.html @@ -2,11 +2,12 @@
-

Новый пароль

-

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

+

Новый пароль

+

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

@if (passwordForm.get("password"); as password) {
Новый пароль

@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)
+
Пароль должен содержать минимум одну цифру (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 }}
} @@ -84,6 +85,7 @@

Новый пароль

} @if (passwordForm.get("passwordRepeated"); as passwordRepeated) {
Новый пароль

@if (credsSubmitInitiated) { @if (passwordRepeated | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @if (passwordRepeated | controlError: "passwordTooShort") { -
+
@if (passwordRepeated.errors) { Пароль должен содержать минимум {{ passwordRepeated.errors["passwordTooShort"]["requiredLength"] }} символов }
} @if (passwordRepeated | controlError: "passwordNoUppercase") { -
+
Пароль должен содержать минимум одну заглавную букву (A-Z)
} @if (passwordRepeated | controlError: "passwordNoLowercase") { -
+
Пароль должен содержать минимум одну строчную букву (a-z)
} @if (passwordRepeated | controlError: "passwordNoNumber") { -
Пароль должен содержать минимум одну цифру (0-9)
+
Пароль должен содержать минимум одну цифру (0-9)
} @if (passwordRepeated | controlError: "passwordNoSpecialChar") { -
+
Пароль должен содержать минимум один специальный символ
} @if (passwordRepeated | controlError: "passwordHasSpaces") { -
Пароль не должен содержать пробелы
+
Пароль не должен содержать пробелы
} @if (passwordRepeated | controlError: "passwordHasSequence") { -
+
Пароль не должен содержать последовательности символов (123456, abcdef и т.д.)
} @if (passwordRepeated | controlError: "passwordHasRepeating") { -
+
Пароль не должен содержать более 2 одинаковых символов подряд
} @if (passwordRepeated | controlError: "unMatch") { -
+
{{ errorMessage.VALIDATION_PASSWORD_UNMATCH }}
} @@ -135,11 +137,17 @@

Новый пароль

}
} @if (errorRequest) { -
+
{{ errorMessage.AUTH_WRONG_AUTH }}
} - + Готово
diff --git a/projects/social_platform/src/app/error/models/error-message.ts b/projects/social_platform/src/app/error/models/error-message.ts index b937ad447..a5b3606b6 100644 --- a/projects/social_platform/src/app/error/models/error-message.ts +++ b/projects/social_platform/src/app/error/models/error-message.ts @@ -40,6 +40,8 @@ export enum ErrorMessage { // Ошибки приглашений в проект USER_NOT_EXISTING = "По данной ссылке пользователь не найден", + USER_IS_LEADER = "Пользователь является лидером проекта", + USER_IS_MEMBER = "Пользователь уже является участником проекта", VALIDATION_PROFILE_LINK = "Введенное значение не соответствует формату ссылки на профиль", // Ошибки оценки проектов diff --git a/projects/social_platform/src/app/office/onboarding/stage-one/stage-one.component.html b/projects/social_platform/src/app/office/onboarding/stage-one/stage-one.component.html index db9276e35..8005668ce 100644 --- a/projects/social_platform/src/app/office/onboarding/stage-one/stage-one.component.html +++ b/projects/social_platform/src/app/office/onboarding/stage-one/stage-one.component.html @@ -36,7 +36,7 @@

Поиск по библио formControlName="speciality" > @if (stageForm.controls["speciality"] | controlError) { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} diff --git a/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.html b/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.html index 09c84559d..a71c115dd 100644 --- a/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.html +++ b/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.html @@ -11,7 +11,7 @@

Привет, {{ profile.firstName }} {{ profile.lastNam @if (stageForm.get("avatar"); as avatar) {
-

Фотография профиля

+

Фотография профиля

Привет, {{ profile.firstName }} {{ profile.lastNam [error]="avatar | controlError" > @if (avatar | controlError: "required") { -
+
{{ errorMessage.EMPTY_AVATAR }}
} @@ -43,6 +43,7 @@

Привет, {{ profile.firstName }} {{ profile.lastNam
Привет, {{ profile.firstName }} {{ profile.lastNam > @if (city | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -96,6 +97,7 @@

Привет, {{ profile.firstName }} {{ profile.lastNam
Привет, {{ profile.firstName }} {{ profile.lastNam @if (educationLevel | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -116,6 +118,7 @@

Привет, {{ profile.firstName }} {{ profile.lastNam
Привет, {{ profile.firstName }} {{ profile.lastNam @if (entryYear | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -133,6 +136,7 @@

Привет, {{ profile.firstName }} {{ profile.lastNam
Привет, {{ profile.firstName }} {{ profile.lastNam @if (completionYear | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -152,6 +156,7 @@

Привет, {{ profile.firstName }} {{ profile.lastNam
Привет, {{ profile.firstName }} {{ profile.lastNam formControlName="organizationName" > @if (organizationName | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -168,6 +173,7 @@

Привет, {{ profile.firstName }} {{ profile.lastNam
Привет, {{ profile.firstName }} {{ profile.lastNam @if (educationStatus | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -185,6 +191,7 @@

Привет, {{ profile.firstName }} {{ profile.lastNam
Привет, {{ profile.firstName }} {{ profile.lastNam > @if (description | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -217,6 +224,7 @@

Привет, {{ profile.firstName }} {{ profile.lastNam
Привет, {{ profile.firstName }} {{ profile.lastNam (click)="addEducation()" > {{ editEducationClick ? "Созранить изменения" : "Добавить образование" }} - + @if(educationItems().length || education.length){ @for (educationItem of education.value; track $index) {
-

+

{{ educationItem.organizationName }} @if(educationItem.entryYear && educationItem.completionYear) { @@ -249,7 +257,7 @@

Привет, {{ profile.firstName }} {{ profile.lastNam class="edit" appIcon icon="edit-pen" - appSquare="24" + appSquare="16" (click)="editEducation($index)" >

@@ -284,6 +292,7 @@

Привет, {{ profile.firstName }} {{ profile.lastNam
Привет, {{ profile.firstName }} {{ profile.lastNam > @if (organizationNameWork | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -318,6 +327,7 @@

Привет, {{ profile.firstName }} {{ profile.lastNam
Привет, {{ profile.firstName }} {{ profile.lastNam @if (entryYearWork | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -336,6 +346,7 @@

Привет, {{ profile.firstName }} {{ profile.lastNam
Привет, {{ profile.firstName }} {{ profile.lastNam @if (completionYearWork | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -357,6 +368,7 @@

Привет, {{ profile.firstName }} {{ profile.lastNam
Привет, {{ profile.firstName }} {{ profile.lastNam formControlName="jobPosition" > @if (jobPosition | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -373,6 +385,7 @@

Привет, {{ profile.firstName }} {{ profile.lastNam
Привет, {{ profile.firstName }} {{ profile.lastNam > @if (descriptionWork | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -404,6 +417,7 @@

Привет, {{ profile.firstName }} {{ profile.lastNam
Привет, {{ profile.firstName }} {{ profile.lastNam (click)="addWork()" > {{ editWorkClick ? "Сохранить изменения" : "Добавить место работы" }} - + @if(workItems().length || workExperience.length){ @for (workItem of workExperience.value; @@ -468,12 +482,13 @@

Привет, {{ profile.firstName }} {{ profile.lastNam >

@if (title | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -492,12 +507,13 @@

Привет, {{ profile.firstName }} {{ profile.lastNam @if (achievements.at(i).get("status"); as status) {
@if (status | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -508,9 +524,14 @@

Привет, {{ profile.firstName }} {{ profile.lastNam } - + Добавить достижение - +

@@ -538,6 +559,7 @@

Привет, {{ profile.firstName }} {{ profile.lastNam
Привет, {{ profile.firstName }} {{ profile.lastNam @if (language | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -555,6 +577,7 @@

Привет, {{ profile.firstName }} {{ profile.lastNam } @if (stageForm.get("languageLevel"); as languageLevel) {
Привет, {{ profile.firstName }} {{ profile.lastNam @if (languageLevel | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -578,6 +601,7 @@

Привет, {{ profile.firstName }} {{ profile.lastNam Привет, {{ profile.firstName }} {{ profile.lastNam (click)="addLanguage()" > {{ editLanguageClick ? "Сохранить изменения" : "Добавить язык" }} - +
@@ -615,7 +639,12 @@

Привет, {{ profile.firstName }} {{ profile.lastNam

- Закончить регистрацию позже Привет, {{ profile.firstName }} {{ profile.lastNam : 'pointer' }" type="submit" + customTypographyClass="text-body-12" [loader]="stageSubmitting()" >Продолжить @@ -656,9 +686,9 @@

Привет, {{ profile.firstName }} {{ profile.lastNam
-

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

+

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

-

{{ isModalErrorYearText() }}.

+

{{ isModalErrorYearText() }}.

diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.html b/projects/social_platform/src/app/office/profile/edit/edit.component.html index e68059ec8..b2c2742af 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.html +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.html @@ -48,7 +48,7 @@ [error]="avatar | controlError" > @if (avatar | controlError: "required") { -
+
{{ errorMessage.EMPTY_AVATAR }}
} @@ -88,7 +88,7 @@ placeholder="У меня много денег, я мамкин инвестор" > @if (usefulToProject | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -100,6 +100,7 @@
@if (firstName | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -116,6 +117,7 @@
@if (lastName | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -136,6 +138,7 @@
@if (birthday | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -152,6 +155,7 @@
@if (phoneNumber | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -167,7 +171,7 @@ }
-

+

Формат телефона должен соответствовать одному из таких форматов: +7 XXX XXX-XX-XX | +375XXXXXXXXX | +995 (XXX) XX-XX-XX

@@ -190,11 +194,11 @@ formControlName="speciality" > - +
@if (speciality | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -208,6 +212,7 @@
@if (city | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -225,13 +230,14 @@ @if (roles | async; as options) { } @if (userType | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -240,6 +246,7 @@
@if (aboutMe | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -262,6 +269,7 @@
@if (entryYear | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -280,6 +288,7 @@
@if (completionYear | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -300,12 +309,13 @@
@if (organizationName | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -314,12 +324,13 @@
@if (description | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -328,6 +339,7 @@
@if (educationLevel | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -345,6 +357,7 @@
@if (educationStatus | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -368,7 +381,7 @@ Студент Московского Политеха @if (isMospolytechStudent | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -381,12 +394,13 @@ Для студентов Московского Политеха @if (studyGroup | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -396,6 +410,7 @@
Добавить образование - + @if(educationItems().length || education.length){ @for (educationItem of education.value; @@ -429,7 +444,7 @@ class="edit" appIcon icon="edit-pen" - appSquare="20" + appSquare="16" (click)="editEducation($index)" >
@@ -446,6 +461,7 @@
@if (entryYearWork | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -464,6 +480,7 @@
@if (completionYearWork | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -485,6 +502,7 @@
@if (organization | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -501,6 +519,7 @@
@if (jobPosition | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -517,6 +536,7 @@
@if (descriptionWork | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -534,6 +554,7 @@
Добавить место работы - + @if(workItems().length || workExperience.length){ @for (workItem of workExperience.value; @@ -561,7 +582,7 @@ {{ workItem.description }} {{ workItem.jobPosition }}

- +
@@ -582,12 +603,13 @@
@if (title | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -605,12 +627,13 @@ @if (achievements.at(i).get("status"); as status) {
@if (status | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -623,9 +646,14 @@
- + Добавить достижение - + } @if (editingStep === 'skills') {
@@ -646,7 +674,7 @@ (optionSelected)="onAddSkill($event)" > - +
@@ -673,6 +701,7 @@
@if (language | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -691,6 +720,7 @@
@if (languageLevel | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -713,6 +743,7 @@
Добавить язык - +
@@ -741,7 +772,7 @@ class="edit" appIcon icon="edit-pen" - appSquare="20" + appSquare="16" (click)="editLanguage($index)" >
@@ -766,7 +797,7 @@ formControlName="additionalRole" > @if (additionalRole | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -899,7 +930,9 @@

Библиотека специальностей

- Сохранить + Сохранить
@@ -922,6 +955,8 @@

Библиотека навыков

- Сохранить + Сохранить
diff --git a/projects/social_platform/src/app/office/program/detail/projects/projects-filter/projects-filter.component.html b/projects/social_platform/src/app/office/program/detail/projects/projects-filter/projects-filter.component.html index 8a9de925c..5d9ab1438 100644 --- a/projects/social_platform/src/app/office/program/detail/projects/projects-filter/projects-filter.component.html +++ b/projects/social_platform/src/app/office/program/detail/projects/projects-filter/projects-filter.component.html @@ -43,6 +43,7 @@

{{ field.label }}

{{ field.label }}

{{ f.value.name }} @if (registerForm.get(f.key); as field) { О проекте

@if (form | controlError: "required"; as error) { -
+
{{ error }}
} diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.html b/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.html index f9cbe5b4c..788bf8b13 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.html +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.html @@ -10,6 +10,7 @@
} @if (achievements.at(i).get("achievementsDate"); as achievementsDate) {
- + diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.html b/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.html index 7f1f07518..063cb7d77 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.html +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.html @@ -16,6 +16,7 @@ >{{ field.label }} @if (additionalForm.get(field.name); as control) { {{ field.label }} } } @if (additionalForm.get(field.name); as control) { @if (control | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.scss index 82c53731b..339e01623 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.scss @@ -100,19 +100,19 @@ ::ng-deep { app-input { input { - @include typography.body-14; + @include typography.body-12; } } app-textarea { textarea { - @include typography.body-14; + @include typography.body-12; } } app-select { .field__input { - @include typography.body-14; + @include typography.body-12; } } } @@ -154,3 +154,7 @@ display: block; } } + +.error { + color: var(--red) !important; +} diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-contacts-step/project-contacts-step.component.html b/projects/social_platform/src/app/office/projects/edit/shared/project-contacts-step/project-contacts-step.component.html index 90eaeb1fa..ff96c8112 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-contacts-step/project-contacts-step.component.html +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-contacts-step/project-contacts-step.component.html @@ -10,7 +10,7 @@ placeholder="https://example.com" > @if (link | controlError: "pattern") { -
+
{{ errorMessage.VALIDATION_PATTERN }}
} diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-contacts-step/project-contacts-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-contacts-step/project-contacts-step.component.scss index fc941cb3b..96d7e17b7 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-contacts-step/project-contacts-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-contacts-step/project-contacts-step.component.scss @@ -44,3 +44,7 @@ display: block; } } + +.error { + color: var(--red) !important; +} 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 ff75a0a52..d48f13589 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 @@ -68,6 +68,7 @@ } @if (trl; as trl) {
- @if ((name | controlError: "required") && projSubmitInitiated) { + @if ((name | controlError: "required") || projSubmitInitiated) {
{{ errorMessage.VALIDATION_REQUIRED }}
@@ -106,10 +107,10 @@ size="big" id="region" formControlName="region" - [error]="(region | controlError) && projSubmitInitiated" + [error]="(region | controlError) || projSubmitInitiated" placeholder="Введите регион" > - @if ((region | controlError: "required") && projSubmitInitiated) { + @if ((region | controlError: "required") || projSubmitInitiated) {
{{ errorMessage.VALIDATION_REQUIRED }}
@@ -165,10 +166,10 @@ size="big" id="problem" formControlName="problem" - [error]="(problem | controlError) && projSubmitInitiated" + [error]="(problem | controlError) || projSubmitInitiated" placeholder="Улучшить качество жизни студентов" > - @if ((problem | controlError: "required") && projSubmitInitiated) { + @if ((problem | controlError: "required") || projSubmitInitiated) {
{{ errorMessage.VALIDATION_REQUIRED }}
@@ -180,11 +181,11 @@ - @if ((description | controlError: "required") && projSubmitInitiated) { + @if ((description | controlError: "required") || projSubmitInitiated) {
{{ errorMessage.VALIDATION_REQUIRED }}
@@ -215,10 +216,11 @@ - @if ((targetAudience | controlError: "required")) { + @if ((targetAudience | controlError: "required") || projSubmitInitiated) {
{{ errorMessage.VALIDATION_REQUIRED }}
diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.scss index 7dd3c3194..9ffc03b73 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.scss @@ -95,3 +95,7 @@ } } } + +.error { + color: var(--red) !important; +} diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.html b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.html index 58cd5f281..08bc50e6c 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.html +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.html @@ -17,12 +17,13 @@ @if (showFields) {
-
+
@if (link; as link) {
} @if (inviteNotExistingError() && inviteSubmitInitiated()) {
- {{ errorMessage.USER_NOT_EXISTING }} + {{ errorMessage.USER_NOT_EXISTING }} либо
+ {{ errorMessage.USER_IS_LEADER }} либо
+ {{ errorMessage.USER_IS_MEMBER }} либо
}
@@ -46,6 +49,7 @@
@if ((specialization | controlError: "required") && inviteSubmitInitiated()) { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @@ -91,7 +95,7 @@
} -


@if (whyMe | controlError: "required") { -
+
{{ errorMessage.VALIDATION_REQUIRED }}
} @if (whyMe | controlError: "maxlength") { -
+
{{ errorMessage.VALIDATION_TOO_LONG }} @if (whyMe.errors) { {{ whyMe.errors["maxlength"]["requiredLength"] }} }
} @if (whyMe | controlError: "minlength") { -
+
{{ errorMessage.VALIDATION_TOO_SHORT }} @if (whyMe.errors) { {{ whyMe.errors["minlength"]["requiredLength"] }} diff --git a/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.html b/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.html index 2e51db552..48f551672 100644 --- a/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.html +++ b/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.html @@ -16,7 +16,7 @@ /> @if (fieldToDisplayMode === "chip" && value()) {
- {{ $any(value())[fieldToDisplay] || value() }} + {{ $any(value())[fieldToDisplay] || value() }}
} @@ -30,16 +30,16 @@ size="24px" > } @if (searchIcon && slimVersion) { - + } @if (searchIcon && !slimVersion) { - + }
@if (isOpen()) {
@if (noResults()) {
    -
  • +
  • {{ "Ничего не найдено :(" }}
@@ -47,7 +47,7 @@
    @for (suggestion of suggestions; track $index) {
  • diff --git a/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.scss b/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.scss index 3d533c241..7518c03c4 100644 --- a/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.scss +++ b/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.scss @@ -12,11 +12,11 @@ &__input { width: 100%; - padding: 8px 10px; + padding: 12px; color: var(--black); background-color: var(--white); border: 1px solid var(--gray); - border-radius: var(--rounded-lg); + border-radius: var(--rounded-xxl); outline: none; transition: all 0.2s; @@ -92,12 +92,10 @@ background-color: var(--light-gray); } - @include typography.body-14; + @include typography.body-12; &-slim { padding: 3px; - - @include typography.body-12; } } @@ -133,11 +131,17 @@ &__icons { position: absolute; top: 50%; - right: 10px; + right: 15px; z-index: 2; display: flex; align-items: center; transform: translateY(-50%); + opacity: 1; + cursor: pointer; + + &:hover { + opacity: 0.6; + } } &__loading-icon { diff --git a/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.ts b/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.ts index 99729b6c9..71b66e79a 100644 --- a/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.ts +++ b/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.ts @@ -111,6 +111,8 @@ export class AutoCompleteInputComponent { /** Состояние ошибки */ @Input() error = false; + @Output() openSkillsFunc = new EventEmitter(); + /** Событие начала поиска */ @Output() searchStart = new EventEmitter(); diff --git a/projects/social_platform/src/app/ui/components/input/input.component.html b/projects/social_platform/src/app/ui/components/input/input.component.html index 8b871bcca..98d0b724b 100644 --- a/projects/social_platform/src/app/ui/components/input/input.component.html +++ b/projects/social_platform/src/app/ui/components/input/input.component.html @@ -1,6 +1,6 @@ -
    +
    @@ -16,12 +16,19 @@ [dropSpecialCharacters]="true" class="field__input" [class.field__input--small]="size === 'small'" - [class.field__input--medium]="size === 'medium'" [class.field__input--big]="size === 'big'" [class.field__input--error]="error" /> @if (error) { - + }
    (); @@ -79,8 +91,25 @@ export class InputComponent implements OnInit, ControlValueAccessor { @Output() enter = new EventEmitter(); ngOnInit(): void { + this.updateCurrentType(); + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes["type"]) { + this.updateCurrentType(); + } + } + + /** Обновляет currentType на основе входящего type */ + private updateCurrentType(): void { this.originalType = this.type; - this.currentType = this.type === "date" ? "text" : this.type; + + // Для поля даты показываем text, пока не в фокусе + if (this.type === "date" && !this.isDateFieldFocused) { + this.currentType = "text"; + } else { + this.currentType = this.type; + } } /** Обработчик ввода текста */ @@ -93,6 +122,7 @@ export class InputComponent implements OnInit, ControlValueAccessor { /** Обработчик потери фокуса */ onBlur(): void { if (this.originalType === "date") { + this.isDateFieldFocused = false; this.currentType = "text"; } this.onTouch(); @@ -101,6 +131,7 @@ export class InputComponent implements OnInit, ControlValueAccessor { /** Обработчик при фокусе */ onFocus(): void { if (this.originalType === "date") { + this.isDateFieldFocused = true; this.currentType = "date"; } } diff --git a/projects/social_platform/src/app/ui/components/select/select.component.html b/projects/social_platform/src/app/ui/components/select/select.component.html index fe3bd0115..f41f1beac 100644 --- a/projects/social_platform/src/app/ui/components/select/select.component.html +++ b/projects/social_platform/src/app/ui/components/select/select.component.html @@ -3,6 +3,7 @@
    + } @else { + }
    @if (isOpen) {
      diff --git a/projects/social_platform/src/app/ui/components/select/select.component.scss b/projects/social_platform/src/app/ui/components/select/select.component.scss index 321ef9311..94c419c96 100644 --- a/projects/social_platform/src/app/ui/components/select/select.component.scss +++ b/projects/social_platform/src/app/ui/components/select/select.component.scss @@ -40,6 +40,10 @@ box-shadow: 0 0 6px rgb(109 40 255 / 30%); } + &--error { + border-color: var(--red) !important; + } + @include typography.body-12; } diff --git a/projects/social_platform/src/app/ui/components/select/select.component.ts b/projects/social_platform/src/app/ui/components/select/select.component.ts index adf937b09..948a5d346 100644 --- a/projects/social_platform/src/app/ui/components/select/select.component.ts +++ b/projects/social_platform/src/app/ui/components/select/select.component.ts @@ -1,5 +1,6 @@ /** @format */ +import { CommonModule } from "@angular/common"; import { Component, ElementRef, @@ -44,7 +45,7 @@ import { ClickOutsideModule } from "ng-click-outside"; }, ], standalone: true, - imports: [ClickOutsideModule, IconComponent], + imports: [ClickOutsideModule, IconComponent, CommonModule], }) export class SelectComponent implements ControlValueAccessor { /** Текст подсказки */ @@ -62,6 +63,8 @@ export class SelectComponent implements ControlValueAccessor { id: number; }[] = []; + @Input() error = false; + /** Состояние открытия выпадающего списка */ isOpen = false; diff --git a/projects/social_platform/src/app/ui/components/textarea/textarea.component.html b/projects/social_platform/src/app/ui/components/textarea/textarea.component.html index 83bf52e9b..87d780945 100644 --- a/projects/social_platform/src/app/ui/components/textarea/textarea.component.html +++ b/projects/social_platform/src/app/ui/components/textarea/textarea.component.html @@ -13,9 +13,11 @@ [placeholder]="placeholder" class="text-body-12 field__input" [class.field__input--small]="size === 'small'" - [class.field__input--medium]="size === 'medium'" [class.field__input--big]="size === 'big'" [class.field__input--error]="error" >{{ value }} + @if (error) { + + }
    diff --git a/projects/social_platform/src/app/ui/components/textarea/textarea.component.scss b/projects/social_platform/src/app/ui/components/textarea/textarea.component.scss index 747f800fd..cf37943fd 100644 --- a/projects/social_platform/src/app/ui/components/textarea/textarea.component.scss +++ b/projects/social_platform/src/app/ui/components/textarea/textarea.component.scss @@ -41,8 +41,8 @@ &__error-icon { position: absolute; - top: 50%; - right: 26px; + top: 15%; + right: 15px; display: flex; align-items: center; justify-content: center; diff --git a/projects/social_platform/src/app/ui/components/textarea/textarea.component.ts b/projects/social_platform/src/app/ui/components/textarea/textarea.component.ts index 240fb91a7..aa9b3f1d9 100644 --- a/projects/social_platform/src/app/ui/components/textarea/textarea.component.ts +++ b/projects/social_platform/src/app/ui/components/textarea/textarea.component.ts @@ -2,6 +2,7 @@ import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from "@angular/core"; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; +import { IconComponent } from "@uilib"; import { AutosizeModule } from "ngx-autosize"; /** @@ -34,7 +35,7 @@ import { AutosizeModule } from "ngx-autosize"; }, ], standalone: true, - imports: [AutosizeModule], + imports: [AutosizeModule, IconComponent], }) export class TextareaComponent implements OnInit, ControlValueAccessor { /** Текст подсказки */ @@ -46,7 +47,7 @@ export class TextareaComponent implements OnInit, ControlValueAccessor { /** Состояние ошибки */ @Input() error = false; - @Input() size: "small" | "medium" | "big" = "small"; + @Input() size: "small" | "big" = "small"; /** Маска (наследуется, но не используется) */ @Input() mask = ""; diff --git a/projects/social_platform/src/assets/icons/svg/box.svg b/projects/social_platform/src/assets/icons/svg/box.svg new file mode 100644 index 000000000..3bc0e3f4a --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/box.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/svg/edit-pen.svg b/projects/social_platform/src/assets/icons/svg/edit-pen.svg index 90723d83f..95d844325 100644 --- a/projects/social_platform/src/assets/icons/svg/edit-pen.svg +++ b/projects/social_platform/src/assets/icons/svg/edit-pen.svg @@ -1,3 +1,3 @@ - - + + diff --git a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg index 61d7f4498..736b25c66 100644 --- a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg +++ b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From c60bf8c5bbcf387e3f2e90f12a8477f81677aded Mon Sep 17 00:00:00 2001 From: Awakich Date: Sat, 6 Sep 2025 22:06:44 +0300 Subject: [PATCH 032/126] add styles to autocomplete-input for icons --- .../autocomplete-input/autocomplete-input.component.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.scss b/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.scss index 7518c03c4..365a8f0d8 100644 --- a/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.scss +++ b/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.scss @@ -135,9 +135,9 @@ z-index: 2; display: flex; align-items: center; - transform: translateY(-50%); - opacity: 1; cursor: pointer; + opacity: 1; + transform: translateY(-50%); &:hover { opacity: 0.6; From 0b694003468bbdad423129c7fc61e49f1241f395 Mon Sep 17 00:00:00 2001 From: Awakich Date: Sun, 7 Sep 2025 01:59:52 +0300 Subject: [PATCH 033/126] add hint component & option hint for input, textarea, button --- .../stage-one/stage-one.component.html | 71 +++--- .../stage-one/stage-one.component.scss | 46 +--- .../stage-one/stage-one.component.ts | 8 +- .../stage-two/stage-two.component.html | 71 +++--- .../stage-two/stage-two.component.scss | 46 +--- .../stage-two/stage-two.component.ts | 8 +- .../stage-zero/stage-zero.component.html | 219 +++++++----------- .../stage-zero/stage-zero.component.scss | 40 +--- .../stage-zero/stage-zero.component.ts | 28 +-- .../detail/profile-detail.component.ts | 2 + .../project-achievement-step.component.html | 13 +- .../project-additional-step.component.html | 15 +- .../project-additional-step.component.scss | 8 +- .../project-additional-step.component.ts | 24 ++ .../project-main-step.component.html | 30 +++ .../project-team-step.component.html | 16 ++ .../project-team-step.component.scss | 10 + .../project-team-step.component.ts | 24 ++ .../project-vacancy-step.component.html | 6 + .../avatar-control.component.html | 17 +- .../avatar-control.component.scss | 9 + .../avatar-control.component.ts | 27 +++ .../components/button/button.component.html | 3 +- .../components/button/button.component.scss | 52 ++--- .../ui/components/button/button.component.ts | 12 +- .../ui/components/input/input.component.html | 16 +- .../ui/components/input/input.component.scss | 17 ++ .../ui/components/input/input.component.ts | 31 ++- .../textarea/textarea.component.html | 13 ++ .../textarea/textarea.component.scss | 12 + .../components/textarea/textarea.component.ts | 28 ++- .../components/tooltip/tooltip.component.html | 20 ++ .../components/tooltip/tooltip.component.scss | 100 ++++++++ .../components/tooltip/tooltip.component.ts | 53 +++++ 34 files changed, 706 insertions(+), 389 deletions(-) create mode 100644 projects/social_platform/src/app/ui/components/tooltip/tooltip.component.html create mode 100644 projects/social_platform/src/app/ui/components/tooltip/tooltip.component.scss create mode 100644 projects/social_platform/src/app/ui/components/tooltip/tooltip.component.ts diff --git a/projects/social_platform/src/app/office/onboarding/stage-one/stage-one.component.html b/projects/social_platform/src/app/office/onboarding/stage-one/stage-one.component.html index 8005668ce..eee5e1b74 100644 --- a/projects/social_platform/src/app/office/onboarding/stage-one/stage-one.component.html +++ b/projects/social_platform/src/app/office/onboarding/stage-one/stage-one.component.html @@ -2,20 +2,20 @@
    -

    Кем хочешь работать?

    - - {{ tooltipAuthText }} +
    +

    Кем хочешь работать?

    + +
    - или diff --git a/projects/social_platform/src/app/office/projects/projects.component.html b/projects/social_platform/src/app/office/projects/projects.component.html index 4f5ee8478..73383a85a 100644 --- a/projects/social_platform/src/app/office/projects/projects.component.html +++ b/projects/social_platform/src/app/office/projects/projects.component.html @@ -37,8 +37,8 @@ (click)="addProject()" customTypographyClass="text-body-12 bar__add-project" > - Добавить проект - + Добавить проект + } @else if (isAll) {
    diff --git a/projects/social_platform/src/app/office/services/project.service.ts b/projects/social_platform/src/app/office/services/project.service.ts index b94861d1c..36060d09c 100644 --- a/projects/social_platform/src/app/office/services/project.service.ts +++ b/projects/social_platform/src/app/office/services/project.service.ts @@ -10,6 +10,7 @@ import { ApiPagination } from "@models/api-pagination.model"; import { Collaborator } from "@office/models/collaborator.model"; import { ProjectAssign } from "@office/projects/models/project-assign.model"; import { projectNewAdditionalProgramVields } from "@office/models/partner-program-fields.model"; +import { Goal } from "@office/models/goals.model"; /** * Сервис для управления проектами @@ -69,6 +70,15 @@ export class ProjectService { return this.apiService.get>(`${this.AUTH_USERS_URL}/projects/`, params); } + /** + * Получает список целей проекта + * + * @returns Observable - объект с массивом целей проекта + */ + getGoals(projectId: number): Observable { + return this.apiService.get(`${this.PROJECTS_URL}/${projectId}/goals/`); + } + /** * BehaviorSubject для хранения счетчиков проектов * Содержит количество собственных проектов, всех проектов и подписок diff --git a/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.html b/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.html index 25302bec4..12d5755b6 100644 --- a/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.html +++ b/projects/social_platform/src/app/office/shared/invite-card/invite-card.component.html @@ -4,7 +4,7 @@
    -

    {{ invite.specialization }}

    +

    {{ invite.role }}

    @if(invite.isAccepted === null) {

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

    } diff --git a/projects/social_platform/src/app/office/shared/skills-group/skills-group.component.html b/projects/social_platform/src/app/office/shared/skills-group/skills-group.component.html index 2b8129235..d5468cdb6 100644 --- a/projects/social_platform/src/app/office/shared/skills-group/skills-group.component.html +++ b/projects/social_platform/src/app/office/shared/skills-group/skills-group.component.html @@ -1,19 +1,35 @@ -
    - {{ title }} - -
    -@if (contentVisible()) { -
    - @for (opt of options; track opt.id) { -
    -
    -
    - + +
    +
    + {{ title }} + +
    + @if (contentVisible()) { +
    + @for (opt of options; track opt.id) { +
    +
    +
    + +
    +
    - + }
    }
    -} diff --git a/projects/social_platform/src/app/office/shared/skills-group/skills-group.component.scss b/projects/social_platform/src/app/office/shared/skills-group/skills-group.component.scss index 567258e2e..41ca97c44 100644 --- a/projects/social_platform/src/app/office/shared/skills-group/skills-group.component.scss +++ b/projects/social_platform/src/app/office/shared/skills-group/skills-group.component.scss @@ -12,14 +12,38 @@ width: 100%; height: 32px; cursor: pointer; + transition: opacity 0.2s ease; i { - transform: rotate(90deg); + transform: rotate(180deg); + transition: transform 0.2s ease; } &--open { i { - transform: rotate(180deg); + transform: rotate(90deg); + } + } + + &--disabled { + opacity: 0.5; + cursor: not-allowed; + pointer-events: none; + } + + &__top { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + + &--open { + width: 50%; + } + + &--disabled { + opacity: 0.5; + cursor: not-allowed; } } } @@ -34,6 +58,13 @@ gap: 10px; align-items: center; cursor: pointer; + transition: opacity 0.2s ease; + + &--disabled { + opacity: 0.5; + cursor: not-allowed; + pointer-events: none; + } &-label { cursor: pointer; @@ -51,11 +82,22 @@ height: 20px; border: 2px solid var(--gray); border-radius: var(--rounded-sm); + transition: all 0.2s ease; &--checked { background-color: var(--accent); border-color: var(--accent); } + + &--disabled { + opacity: 0.5; + cursor: not-allowed; + + &.checkbox__field--checked { + background-color: var(--gray); + border-color: var(--gray); + } + } } i { diff --git a/projects/social_platform/src/app/office/shared/skills-group/skills-group.component.ts b/projects/social_platform/src/app/office/shared/skills-group/skills-group.component.ts index a6cab1687..cce0b02be 100644 --- a/projects/social_platform/src/app/office/shared/skills-group/skills-group.component.ts +++ b/projects/social_platform/src/app/office/shared/skills-group/skills-group.component.ts @@ -1,5 +1,4 @@ /** @format */ - import { CommonModule } from "@angular/common"; import { ChangeDetectionStrategy, @@ -22,19 +21,17 @@ import { Skill } from "@office/models/skill"; * - Синхронизирует состояние выбранных навыков с внешним состоянием * - Использует Angular Signals для реактивности * - Использует OnPush стратегию для оптимизации производительности + * - Поддерживает disabled состояние когда открыты другие группы * * Входные параметры: * @Input options - массив доступных навыков (обязательный) * @Input selected - массив выбранных навыков (обязательный) * @Input title - заголовок группы навыков (обязательный) + * @Input disabled - флаг отключения взаимодействия с группой * * Выходные события: * @Output optionToggled - событие переключения навыка, передает навык который был включен/выключен - * - * Внутренние свойства: - * - _options - сигнал с массивом навыков и их состоянием выбора - * - _selected - сигнал с массивом выбранных навыков - * - contentVisible - сигнал видимости содержимого группы + * @Output groupToggled - событие переключения видимости группы */ @Component({ selector: "app-skills-group", @@ -76,6 +73,9 @@ export class SkillsGroupComponent { } @Input({ required: true }) title!: string; + @Input() hasOpenGroups = false; + @Input() disabled = false; + @Output() groupToggled = new EventEmitter(); @Output() optionToggled = new EventEmitter(); _options = signal<(Skill & { checked?: boolean })[]>([]); @@ -84,8 +84,26 @@ export class SkillsGroupComponent { /** * Переключение видимости содержимого группы + * Теперь учитывает disabled состояние + */ + toggleContentVisible() { + if (this.disabled) { + return; + } + + this.contentVisible.update(val => !val); + this.groupToggled.emit(this.contentVisible()); + } + + /** + * Обработка клика по опции навыка + * Теперь учитывает disabled состояние */ - toggleContentVisible(): void { - this.contentVisible.update(visible => !visible); + onOptionClick(opt: Skill) { + if (this.disabled) { + return; + } + + this.optionToggled.emit(opt); } } diff --git a/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.html b/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.html index a1b128b1d..051653088 100644 --- a/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.html +++ b/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.html @@ -29,18 +29,12 @@

    {{ vacancy.role }}

    - - + + - - + +
    diff --git a/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.html b/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.html index f59d48c16..ae07d363e 100644 --- a/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.html +++ b/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.html @@ -43,7 +43,6 @@ [text]="tooltipText" [isVisible]="isTooltipVisible" [position]="tooltipPosition" - [iconSize]="'24'" [tooltipWidth]="tooltipWidth" customClass="control__tooltip-icon" (show)="showTooltip()" diff --git a/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.scss b/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.scss index f85217ca7..94db37c3e 100644 --- a/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.scss +++ b/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.scss @@ -70,8 +70,8 @@ &__tooltip-wrapper { position: absolute; - top: 10%; - right: -10px; + top: 15%; + right: -5px; z-index: 10; transform: translateY(-50%); } diff --git a/projects/social_platform/src/app/ui/components/input/input.component.html b/projects/social_platform/src/app/ui/components/input/input.component.html index 5857b9211..b1b4b1686 100644 --- a/projects/social_platform/src/app/ui/components/input/input.component.html +++ b/projects/social_platform/src/app/ui/components/input/input.component.html @@ -4,6 +4,16 @@
    + @if (type === 'radio') { + + } @else { - @if (error) { + } @if (error) { (); + /** Событие изменения состояния радио (для внешних обработчиков) */ + @Output() change = new EventEmitter(); + ngOnInit(): void { this.updateCurrentType(); } @@ -119,6 +130,18 @@ export class InputComponent implements OnInit, OnChanges, ControlValueAccessor { } } + /** Обработчик для радио */ + onRadioChange(event: Event): void { + if (this.type === "radio") { + const target = event.target as HTMLInputElement; + this.value = target.value; + this.onChange(this.value); + this.appValueChange.emit(this.value); + this.change.emit(event); // Эмитим событие для внешних обработчиков + this.onTouch(); + } + } + /** Обновляет currentType на основе входящего type */ private updateCurrentType(): void { this.originalType = this.type; diff --git a/projects/social_platform/src/app/ui/components/loader/loader.component.ts b/projects/social_platform/src/app/ui/components/loader/loader.component.ts index 28f92957b..585782022 100644 --- a/projects/social_platform/src/app/ui/components/loader/loader.component.ts +++ b/projects/social_platform/src/app/ui/components/loader/loader.component.ts @@ -29,7 +29,7 @@ export class LoaderComponent implements OnInit { @Input() speed = "1s"; /** Размер индикатора */ - @Input() size = "47px"; + @Input() size = "30px"; /** Цвет индикатора */ @Input() color = "white"; diff --git a/projects/social_platform/src/app/ui/components/snackbar/snackbar.component.html b/projects/social_platform/src/app/ui/components/snackbar/snackbar.component.html index 0367aa4e1..ae6267704 100644 --- a/projects/social_platform/src/app/ui/components/snackbar/snackbar.component.html +++ b/projects/social_platform/src/app/ui/components/snackbar/snackbar.component.html @@ -12,6 +12,7 @@ @slideInOut > {{ snack.text }} +
  • }
diff --git a/projects/social_platform/src/app/ui/components/snackbar/snackbar.component.scss b/projects/social_platform/src/app/ui/components/snackbar/snackbar.component.scss index 4743fe5bb..7bab1e25b 100644 --- a/projects/social_platform/src/app/ui/components/snackbar/snackbar.component.scss +++ b/projects/social_platform/src/app/ui/components/snackbar/snackbar.component.scss @@ -3,10 +3,10 @@ .snackbar { position: absolute; right: 0; - bottom: 0; + top: 0; z-index: 20; padding-right: 50px; - padding-bottom: 50px; + padding-top: 50px; overflow-x: hidden; &__item { @@ -17,11 +17,20 @@ } .item { - padding: 12px; - border-radius: 12px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 15px; + padding: 20px; + border-radius: 10px; &--success { color: var(--white); background-color: var(--green); } + + &--error { + color: var(--white); + background-color: var(--red); + } } diff --git a/projects/social_platform/src/app/ui/components/snackbar/snackbar.component.ts b/projects/social_platform/src/app/ui/components/snackbar/snackbar.component.ts index 0decd35d8..abb77f14f 100644 --- a/projects/social_platform/src/app/ui/components/snackbar/snackbar.component.ts +++ b/projects/social_platform/src/app/ui/components/snackbar/snackbar.component.ts @@ -6,6 +6,7 @@ import { Snack } from "@ui/models/snack.model"; import { Subscription } from "rxjs"; import { AnimationService } from "@ui/services/animation.service"; import { CommonModule } from "@angular/common"; +import { IconComponent } from "@uilib"; /** * Компонент для отображения всплывающих уведомлений (snackbar). @@ -25,7 +26,7 @@ import { CommonModule } from "@angular/common"; templateUrl: "./snackbar.component.html", styleUrl: "./snackbar.component.scss", animations: [AnimationService.slideInOut], - imports: [CommonModule], + imports: [CommonModule, IconComponent], standalone: true, }) export class SnackbarComponent implements OnInit, OnDestroy { diff --git a/projects/social_platform/src/app/ui/components/textarea/textarea.component.html b/projects/social_platform/src/app/ui/components/textarea/textarea.component.html index 14d9c90f8..4ae13ee29 100644 --- a/projects/social_platform/src/app/ui/components/textarea/textarea.component.html +++ b/projects/social_platform/src/app/ui/components/textarea/textarea.component.html @@ -25,7 +25,6 @@ [text]="tooltipText" [isVisible]="isTooltipVisible" [position]="tooltipPosition" - [iconSize]="'24'" [tooltipWidth]="tooltipWidth" customClass="field__tooltip-icon" (show)="showTooltip()" diff --git a/projects/social_platform/src/app/ui/components/textarea/textarea.component.scss b/projects/social_platform/src/app/ui/components/textarea/textarea.component.scss index 9ed065f7c..c89bd2ab6 100644 --- a/projects/social_platform/src/app/ui/components/textarea/textarea.component.scss +++ b/projects/social_platform/src/app/ui/components/textarea/textarea.component.scss @@ -55,8 +55,8 @@ &__tooltip-wrapper { position: absolute; - top: 20%; - right: 20px; + top: 15%; + right: 15px; z-index: 10; transform: translateY(-50%); } diff --git a/projects/social_platform/src/app/ui/components/tooltip/tooltip.component.ts b/projects/social_platform/src/app/ui/components/tooltip/tooltip.component.ts index 99a62b531..64fdcca7f 100644 --- a/projects/social_platform/src/app/ui/components/tooltip/tooltip.component.ts +++ b/projects/social_platform/src/app/ui/components/tooltip/tooltip.component.ts @@ -37,7 +37,7 @@ export class TooltipComponent { @Input() position: "left" | "right" = "right"; /** Размер иконки */ - @Input() iconSize = "24"; + @Input() iconSize = "16"; /** Ширина подсказки */ @Input() tooltipWidth = 250; diff --git a/projects/social_platform/src/assets/icons/svg/add-person.svg b/projects/social_platform/src/assets/icons/svg/add-person.svg new file mode 100644 index 000000000..622cd5b0b --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/add-person.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/svg/hint.svg b/projects/social_platform/src/assets/icons/svg/hint.svg index 42dc91c30..c997b6364 100644 --- a/projects/social_platform/src/assets/icons/svg/hint.svg +++ b/projects/social_platform/src/assets/icons/svg/hint.svg @@ -1,4 +1,3 @@ - - - + + diff --git a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg index 736b25c66..a204a7498 100644 --- a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg +++ b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/projects/social_platform/src/styles/_typography.scss b/projects/social_platform/src/styles/_typography.scss index c396582c8..75ccc947c 100644 --- a/projects/social_platform/src/styles/_typography.scss +++ b/projects/social_platform/src/styles/_typography.scss @@ -200,7 +200,7 @@ font-family: Mont, sans-serif; font-size: 14px; font-style: normal; - font-weight: 600; + font-weight: 400; line-height: 150%; } From a7fdb95dd4d0d5344d5c32967e1af1aa32233530 Mon Sep 17 00:00:00 2001 From: Awakich Date: Tue, 9 Sep 2025 00:20:43 +0300 Subject: [PATCH 038/126] add styles to snackbar, skills-group, input --- .../project-main-step.component.scss | 20 +++++++------- .../project-team-step.component.scss | 2 +- .../skills-group/skills-group.component.scss | 16 +++++------ .../ui/components/input/input.component.scss | 27 +++++++++---------- .../snackbar/snackbar.component.scss | 6 ++--- 5 files changed, 34 insertions(+), 37 deletions(-) diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.scss index 1a127b427..fd6bf8849 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.scss @@ -99,8 +99,8 @@ &__links { display: flex; gap: 20px; - width: 100%; align-items: center; + width: 100%; &--wrapper { display: flex; @@ -144,11 +144,11 @@ flex-direction: column; align-items: center; justify-content: center; + width: 700px; + max-width: 100%; max-height: calc(100vh - 40px); padding: 40px 0 80px; overflow-y: auto; - max-width: 100%; - width: 700px; &__cross { position: absolute; @@ -183,12 +183,12 @@ .selected-leader { display: flex; - align-items: center; gap: 8px; + align-items: center; padding: 4px 8px; + cursor: pointer; border: 1px solid #e0e0e0; border-radius: 4px; - cursor: pointer; transition: background-color 0.2s; &:hover { @@ -199,24 +199,24 @@ .invite { &__item { display: flex; - justify-content: space-between; - align-items: center; flex-grow: 1; - margin-bottom: 12px; + align-items: center; + justify-content: space-between; width: 300px; + margin-bottom: 12px; &--info { display: flex; - align-items: center; gap: 20px; + align-items: center; } } &__team { display: flex; flex-direction: column; + gap: 10px; align-items: flex-start; justify-content: space-between; - gap: 10px; } } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss index 072a2e835..6b7147db3 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss @@ -112,8 +112,8 @@ } &--wrapper { - flex-direction: column-reverse; flex-basis: 40%; + flex-direction: column-reverse; } } diff --git a/projects/social_platform/src/app/office/shared/skills-group/skills-group.component.scss b/projects/social_platform/src/app/office/shared/skills-group/skills-group.component.scss index 41ca97c44..bf79aa605 100644 --- a/projects/social_platform/src/app/office/shared/skills-group/skills-group.component.scss +++ b/projects/social_platform/src/app/office/shared/skills-group/skills-group.component.scss @@ -15,8 +15,8 @@ transition: opacity 0.2s ease; i { - transform: rotate(180deg); transition: transform 0.2s ease; + transform: rotate(180deg); } &--open { @@ -26,15 +26,15 @@ } &--disabled { - opacity: 0.5; - cursor: not-allowed; pointer-events: none; + cursor: not-allowed; + opacity: 0.5; } &__top { display: flex; - justify-content: space-between; align-items: center; + justify-content: space-between; width: 100%; &--open { @@ -42,8 +42,8 @@ } &--disabled { - opacity: 0.5; cursor: not-allowed; + opacity: 0.5; } } } @@ -61,9 +61,9 @@ transition: opacity 0.2s ease; &--disabled { - opacity: 0.5; - cursor: not-allowed; pointer-events: none; + cursor: not-allowed; + opacity: 0.5; } &-label { @@ -90,8 +90,8 @@ } &--disabled { - opacity: 0.5; cursor: not-allowed; + opacity: 0.5; &.checkbox__field--checked { background-color: var(--gray); diff --git a/projects/social_platform/src/app/ui/components/input/input.component.scss b/projects/social_platform/src/app/ui/components/input/input.component.scss index b480dc94d..c1c600cbd 100644 --- a/projects/social_platform/src/app/ui/components/input/input.component.scss +++ b/projects/social_platform/src/app/ui/components/input/input.component.scss @@ -47,7 +47,6 @@ @include responsive.apply-desktop { @include typography.body-12; } - } &__input[type="date"]::-webkit-inner-spin-button, @@ -57,45 +56,43 @@ } &__input[type="radio"] { - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; - + position: relative; width: 20px; height: 20px; padding: 8px; + cursor: pointer; border: 1px solid var(--dark-grey); border-radius: 50%; - cursor: pointer; - position: relative; transition: border-color 0.2s, background-color 0.2s; + appearance: none; + appearance: none; + appearance: none; &:hover::after { - content: ""; position: absolute; top: 50%; left: 50%; width: 8px; height: 8px; + content: ""; background-color: var(--accent); border-radius: 50%; transform: translate(-50%, -50%); } &:checked { - border-color: var(--accent); - background-color: var(--accent); z-index: 1; + background-color: var(--accent); + border-color: var(--accent); &::after { - background-color: var(--dark-grey); - width: 8px; - height: 8px; - - content: ""; position: absolute; top: 50%; left: 50%; + width: 8px; + height: 8px; + content: ""; + background-color: var(--dark-grey); border-radius: 50%; transform: translate(-50%, -50%); } diff --git a/projects/social_platform/src/app/ui/components/snackbar/snackbar.component.scss b/projects/social_platform/src/app/ui/components/snackbar/snackbar.component.scss index 7bab1e25b..abbf125c3 100644 --- a/projects/social_platform/src/app/ui/components/snackbar/snackbar.component.scss +++ b/projects/social_platform/src/app/ui/components/snackbar/snackbar.component.scss @@ -2,11 +2,11 @@ .snackbar { position: absolute; - right: 0; top: 0; + right: 0; z-index: 20; - padding-right: 50px; padding-top: 50px; + padding-right: 50px; overflow-x: hidden; &__item { @@ -18,9 +18,9 @@ .item { display: flex; + gap: 15px; align-items: center; justify-content: space-between; - gap: 15px; padding: 20px; border-radius: 10px; From 0a55bafee292ca8e1d6bc8019c67ad3efe732c95 Mon Sep 17 00:00:00 2001 From: Awakich Date: Tue, 9 Sep 2025 00:21:08 +0300 Subject: [PATCH 039/126] add styles to input --- .../src/app/ui/components/input/input.component.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/social_platform/src/app/ui/components/input/input.component.scss b/projects/social_platform/src/app/ui/components/input/input.component.scss index c1c600cbd..f7f34805d 100644 --- a/projects/social_platform/src/app/ui/components/input/input.component.scss +++ b/projects/social_platform/src/app/ui/components/input/input.component.scss @@ -66,7 +66,6 @@ transition: border-color 0.2s, background-color 0.2s; appearance: none; appearance: none; - appearance: none; &:hover::after { position: absolute; From b5a2a86ee504d5aacb2fec22959abf90c4ec41b3 Mon Sep 17 00:00:00 2001 From: Awakich Date: Tue, 9 Sep 2025 00:21:57 +0300 Subject: [PATCH 040/126] run format --- .../src/app/ui/components/input/input.component.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/social_platform/src/app/ui/components/input/input.component.scss b/projects/social_platform/src/app/ui/components/input/input.component.scss index f7f34805d..3cb5c564e 100644 --- a/projects/social_platform/src/app/ui/components/input/input.component.scss +++ b/projects/social_platform/src/app/ui/components/input/input.component.scss @@ -65,7 +65,6 @@ border-radius: 50%; transition: border-color 0.2s, background-color 0.2s; appearance: none; - appearance: none; &:hover::after { position: absolute; From 0961b163f19605ab9a824c19216e0c87610d16be Mon Sep 17 00:00:00 2001 From: Awakich Date: Tue, 9 Sep 2025 12:58:11 +0300 Subject: [PATCH 041/126] add crud logic for goals & mapping for goals before push to backend --- .../src/app/office/models/goals.model.ts | 13 +- .../office/projects/edit/edit.component.ts | 65 ++-- .../edit/services/project-form.service.ts | 45 +-- .../edit/services/project-goals.service.ts | 307 +++++++++++++++--- .../project-main-step.component.html | 23 +- .../project-main-step.component.scss | 19 +- .../project-main-step.component.ts | 41 +-- .../app/office/services/project.service.ts | 18 +- 8 files changed, 354 insertions(+), 177 deletions(-) diff --git a/projects/social_platform/src/app/office/models/goals.model.ts b/projects/social_platform/src/app/office/models/goals.model.ts index 8fa3c48d2..75eb9fc02 100644 --- a/projects/social_platform/src/app/office/models/goals.model.ts +++ b/projects/social_platform/src/app/office/models/goals.model.ts @@ -10,19 +10,26 @@ * @format */ -class responsibleInfo { +class ResponsibleInfo { id!: number; firstName!: string; lastName!: string; avatar!: string | null; } +export class GoalPostForm { + title!: string; + completionDate!: string; + responsible!: number; + isDone!: boolean; +} + export class Goal { id!: number; project!: number; title!: string; completionDate!: string; - responsibleId!: number; - responsibleInfo!: responsibleInfo; + responsible!: number; + responsibleInfo!: ResponsibleInfo; isDone!: boolean; } 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 18307f27f..e8b5d0c1f 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 @@ -49,7 +49,7 @@ 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 } from "@office/models/goals.model"; +import { Goal, GoalPostForm } from "@office/models/goals.model"; import { ProjectGoalService } from "./services/project-goals.service"; import { SnackbarService } from "@ui/services/snackbar.service"; @@ -62,24 +62,8 @@ import { SnackbarService } from "@ui/services/snackbar.service"; * - Загрузка файлов (презентация, обложка, аватар) * - Создание и редактирование вакансий с навыками * - Приглашение участников в команду - * - Управление достижениями и ссылками проекта + * - Управление достижениями, ссылками и целями проекта * - Сохранение как черновик или публикация - * - * Принимает: - * - ID проекта из URL параметров - * - Данные проекта и приглашений через resolver - * - Query параметр editingStep для определения активного шага - * - * Возвращает: - * - Интерфейс редактирования с навигацией по шагам - * - Формы для ввода данных проекта - * - Модальные окна для управления навыками и приглашениями - * - * Особенности: - * - Реактивные формы с валидацией - * - Динамическое управление массивами (достижения, ссылки) - * - Интеграция с внешними сервисами (навыки, программы) - * - Поддержка автокомплита для навыков */ @Component({ selector: "app-edit", @@ -121,7 +105,8 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { private readonly projectAchievementsService: ProjectAchievementsService, private readonly snackBarService: SnackbarService, private readonly skillsService: SkillsService, - private readonly projectAdditionalService: ProjectAdditionalService + private readonly projectAdditionalService: ProjectAdditionalService, + private readonly projectGoalService: ProjectGoalService // Добавляем ProjectGoalService ) {} // Получаем форму проекта из сервиса @@ -134,7 +119,7 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { return this.projectVacancyService.getVacancyForm(); } - // Получаем форму вакансии из сервиса + // Получаем форму дополнительных полей из сервиса get additionalForm(): FormGroup { return this.projectAdditionalService.getAdditionalForm(); } @@ -144,7 +129,7 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { return this.projectFormService.achievements; } - // Id редатируемой части проекта + // Id редактируемой части проекта get editIndex() { return this.projectFormService.editIndex; } @@ -194,6 +179,9 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { ngOnDestroy(): void { this.profile$?.unsubscribe(); this.subscriptions.forEach($ => $?.unsubscribe()); + + // Сброс состояния ProjectGoalService при уничтожении компонента + this.projectGoalService.reset(); } // Опции для программных тегов @@ -321,6 +309,8 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { // Очистка основной формы this.projectFormService.clearAllValidationErrors(); this.projectAchievementsService.clearAllAchievementsErrors(this.achievements); + + // Очистка ошибок целей теперь входит в clearAllValidationErrors() ProjectFormService } /** @@ -399,8 +389,6 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { this.projectVacancyService.submitVacancy(projectId); } - console.log(payload.goals); - if ( !this.validationService.getFormValidation(this.projectForm) || !this.validationService.getFormValidation(this.additionalForm) || @@ -410,17 +398,22 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { } this.setProjFormIsSubmitting(true); - // this.projectService.updateProject(projectId, payload).subscribe({ - // next: () => { - // this.snackBarService.success("Данные успешно сохранены"); - // this.setProjFormIsSubmitting(false); - // this.router.navigateByUrl(`/office/projects/my`); - // }, - // error: () => { - // this.setProjFormIsSubmitting(false); - // this.snackBarService.error("Что-то пошло не так!"); - // }, - // }); + + this.projectService.updateProject(projectId, payload).subscribe({ + next: () => { + this.projectGoalService.saveGoals(projectId).subscribe({ + next: () => { + this.snackBarService.success("Данные успешно сохранены"); + this.setProjFormIsSubmitting(false); + this.router.navigateByUrl(`/office/projects/my`); + }, + }); + }, + error: () => { + this.setProjFormIsSubmitting(false); + this.snackBarService.error("Что-то пошло не так!"); + }, + }); } // Методы для работы с модальными окнами @@ -588,11 +581,11 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { ) .subscribe(([project, goals, invites]: [Project, Goal[], Invite[]]) => { // Используем сервис для инициализации данных проекта - this.projectFormService.initializeProjectData(project, goals); + this.projectFormService.initializeProjectData(project); + this.projectGoalService.initializeGoalsFromProject(goals); this.projectTeamService.setInvites(invites); this.projectTeamService.setCollaborators(project.collaborators); - // Инициализируем дополнительные поля через сервис if (project.partnerProgram) { this.isCompetitive = project.partnerProgram.canSubmit; this.isProjectBoundToProgram = !!project.partnerProgram.programId; 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 f8a5b574d..ff8e2da54 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 @@ -10,13 +10,11 @@ import { ValidatorFn, } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; -import { Goal } from "@office/models/goals.model"; import { PartnerProgramFields } from "@office/models/partner-program-fields.model"; import { Project } from "@office/models/project.model"; import { ProjectService } from "@office/services/project.service"; import { stripNullish } from "@utils/stripNull"; import { concatMap, filter } from "rxjs"; - /** * Сервис для управления основной формой проекта и формой дополнительных полей партнерской программы. * Обеспечивает создание, инициализацию, валидацию, автосохранение, сброс и получение данных форм. @@ -60,11 +58,6 @@ export class ProjectFormService { title: [""], status: [""], - goals: this.fb.array([]), - goalName: [""], - goalDate: [""], - goalLeader: [null], - draft: [null], }); @@ -99,7 +92,7 @@ export class ProjectFormService { * Заполняет основную форму данными существующего проекта. * @param project экземпляр Project с текущими данными */ - public initializeProjectData(project: Project, goals: Goal[]): void { + public initializeProjectData(project: Project): void { // Заполняем простые поля this.projectForm.patchValue({ imageAddress: project.imageAddress, @@ -122,10 +115,7 @@ export class ProjectFormService { } this.populateLinksFormArray(project.links || []); - this.populateAchievementsFormArray(project.achievements || []); - - this.populateGoalsFormArray(goals || []); } /** @@ -165,28 +155,6 @@ export class ProjectFormService { }); } - /** - * Заполняет FormArray цели данными из проекта - * @param goals массив целей из проекта - */ - private populateGoalsFormArray(goals: any[]): void { - const goalsFormArray = this.projectForm.get("goals") as FormArray; - - while (goalsFormArray.length !== 0) { - goalsFormArray.removeAt(0); - } - - goals.forEach(goal => { - const goalsGroup = this.fb.group({ - goalName: [goal.goalName || "", Validators.required], - goalDate: [goal.goalDate || "", Validators.required], - goalLeader: [goal.goalLeader || "", Validators.required], - isDone: false, - }); - goalsFormArray.push(goalsGroup); - }); - } - /** * Возвращает основную форму проекта. * @returns FormGroup экземпляр формы проекта @@ -338,8 +306,6 @@ export class ProjectFormService { public resetForms(): void { this.projectForm.reset(); this.additionalForm?.reset(); - - // Очищаем FormArray this.clearFormArrays(); } @@ -360,11 +326,14 @@ export class ProjectFormService { } /** - * Проверяет валидность обеих форм (основной и дополнительной). - * @returns true если обе формы валидны + * Проверяет валидность обеих форм (основной и дополнительной) включая цели. + * @returns true если все формы валидны */ public validateAllForms(): boolean { - return this.validateForm() && this.validateAdditionalForm(); + const mainFormValid = this.validateForm(); + const additionalFormValid = this.validateAdditionalForm(); + + return mainFormValid && additionalFormValid; } /** diff --git a/projects/social_platform/src/app/office/projects/edit/services/project-goals.service.ts b/projects/social_platform/src/app/office/projects/edit/services/project-goals.service.ts index c44984fd8..bc629cc7d 100644 --- a/projects/social_platform/src/app/office/projects/edit/services/project-goals.service.ts +++ b/projects/social_platform/src/app/office/projects/edit/services/project-goals.service.ts @@ -1,33 +1,54 @@ /** @format */ import { inject, Injectable, signal } from "@angular/core"; -import { FormArray, FormBuilder, FormControl, FormGroup } from "@angular/forms"; +import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from "@angular/forms"; import { ProjectFormService } from "./project-form.service"; +import { Goal, GoalPostForm } from "@office/models/goals.model"; +import { catchError, forkJoin, map, of, tap } from "rxjs"; +import { ProjectService } from "@office/services/project.service"; /** - * Сервис для управления целями проекта и выбором лидера(ответсвенного за цель) и даты цели - * Предоставляет методы для добавления, даления целей, - * а также очистки ошибок валидации. + * Сервис для управления целями проекта + * Предоставляет полный набор методов для работы с целями: + * - инициализация, добавление, редактирование, удаление + * - валидация и очистка ошибок + * - управление состоянием модального окна выбора лидера */ @Injectable({ providedIn: "root", }) export class ProjectGoalService { - /** FormBuilder для создания FormGroup элементов */ private readonly fb = inject(FormBuilder); - - /** Сервис для управления индексом редактируемого достижения */ + private goalForm!: FormGroup; private readonly projectFormService = inject(ProjectFormService); - - /** Сигнал для хранения списка целей (массив объектов) */ + private readonly projectService = inject(ProjectService); public readonly goalItems = signal([]); + + /** Флаг инициализации сервиса */ private initialized = false; + public readonly goalLeaderShowModal = signal(false); + public readonly activeGoalIndex = signal(null); + public readonly selectedLeaderId = signal(""); + + constructor() { + this.initializeGoalForm(); + } + + private initializeGoalForm(): void { + this.goalForm = this.fb.group({ + goals: this.fb.array([]), + title: [null], + completionDate: [null], + responsible: [null], + }); + } + /** * Инициализирует сигнал goalItems из данных FormArray * Вызывается при первом обращении к данным */ - private initializeGoalItems(goalFormArray: FormArray): void { + public initializeGoalItems(goalFormArray: FormArray): void { if (this.initialized) return; if (goalFormArray && goalFormArray.length > 0) { @@ -47,103 +68,202 @@ export class ProjectGoalService { } /** - * Получает основную форму проекта + * Инициализирует цели из данных проекта + * Заполняет FormArray целей данными из проекта + */ + public initializeGoalsFromProject(goals: Goal[]): void { + const goalsFormArray = this.goals; + + while (goalsFormArray.length !== 0) { + goalsFormArray.removeAt(0); + } + + if (goals && Array.isArray(goals)) { + goals.forEach(goal => { + const goalsGroup = this.fb.group({ + id: [goal.id ?? null], + title: [goal.title || "", Validators.required], + completionDate: [goal.completionDate || "", Validators.required], + responsible: [goal.responsibleInfo?.id?.toString() || "", Validators.required], + isDone: [goal.isDone || false], + }); + goalsFormArray.push(goalsGroup); + }); + + this.syncGoalItems(goalsFormArray); + } else { + this.goalItems.set([]); + } + } + + /** + * Возвращает форму целей. + * @returns FormGroup экземпляр формы целей */ - private get projectForm(): FormGroup { - return this.projectFormService.getForm(); + public getForm(): FormGroup { + return this.goalForm; } /** * Получает FormArray целей */ public get goals(): FormArray { - return this.projectForm.get("goals") as FormArray; + return this.goalForm.get("goals") as FormArray; } /** * Получает FormControl для поля ввода названия цели */ public get goalName(): FormControl { - return this.projectForm.get("goalName") as FormControl; + return this.goalForm.get("title") as FormControl; } /** * Получает FormControl для поля ввода даты цели */ public get goalDate(): FormControl { - return this.projectForm.get("goalDate") as FormControl; + return this.goalForm.get("completionDate") as FormControl; } /** - * Получает FormControl для поля лидера(исполнителя/ответсвенного) цели + * Получает FormControl для поля лидера(исполнителя/ответственного) цели */ public get goalLeader(): FormControl { - return this.projectForm.get("goalLeader") as FormControl; + return this.goalForm.get("responsible") as FormControl; } /** - * Добавляет новую цель или сохраняет изменения существующего. - * @param goalFormArray FormArray, содержащий формы достижений - * @param projectForm основная форма проекта (FormGroup) + * Добавляет новую цель или сохраняет изменения существующей. + * @param goalName - название цели (опционально) + * @param goalDate - дата цели (опционально) + * @param goalLeader - лидер цели (опционально) */ - public addGoal(goalFormArray: FormArray, projectForm: FormGroup): void { + public addGoal(goalName?: string, goalDate?: string, goalLeader?: string): void { + const goalFormArray = this.goals; + this.initializeGoalItems(goalFormArray); - const goalName = projectForm.get("goalName")?.value; - const goalDate = projectForm.get("goalDate")?.value; - const goalLeader = projectForm.get("goalLeader")?.value; + const name = goalName || this.goalForm.get("title")?.value; + const date = goalDate || this.goalForm.get("completionDate")?.value; + const leader = goalLeader || this.goalForm.get("responsible")?.value; - if (!goalName || !goalDate || goalName.trim().length === 0 || goalDate.trim().length === 0) { + if (!name || !date || name.trim().length === 0 || date.trim().length === 0) { return; } - const goalItems = this.fb.group({ - title: goalName.trim(), - completionDate: goalDate.trim(), - responsibleInfo: goalLeader, - isDone: false, + const goalItem = this.fb.group({ + title: [name.trim(), Validators.required], + completionDate: [date.trim(), Validators.required], + responsible: [leader, Validators.required], + isDone: [false], }); const editIdx = this.projectFormService.editIndex(); if (editIdx !== null) { this.goalItems.update(items => { const updated = [...items]; - updated[editIdx] = goalItems.value; + updated[editIdx] = goalItem.value; return updated; }); - goalFormArray.at(editIdx).patchValue(goalItems.value); + goalFormArray.at(editIdx).patchValue(goalItem.value); this.projectFormService.editIndex.set(null); } else { - this.goalItems.update(items => [...items, goalItems.value]); - goalFormArray.push(goalItems); + this.goalItems.update(items => [...items, goalItem.value]); + goalFormArray.push(goalItem); } - - // Очищаем поля ввода формы проекта - projectForm.get("goalName")?.reset(); - projectForm.get("goalName")?.setValue(""); - - projectForm.get("goalDate")?.reset(); - projectForm.get("goalDate")?.setValue(""); - - projectForm.get("goalLeader")?.reset(); - projectForm.get("goalLeader")?.setValue(""); } /** * Удаляет цель по указанному индексу. - * @param index индекс удаляемого достижения - * @param goalFormArray FormArray достижений + * @param index индекс удаляемой цели */ - public removeGoal(index: number, goalFormArray: FormArray): void { + public removeGoal(index: number): void { + const goalFormArray = this.goals; + this.goalItems.update(items => items.filter((_, i) => i !== index)); goalFormArray.removeAt(index); } + /** + * Получает выбранного лидера для конкретной цели + * @param goalIndex - индекс цели + * @param collaborators - список коллабораторов + */ + public getSelectedLeaderForGoal(goalIndex: number, collaborators: any[]) { + const goalFormGroup = this.goals.at(goalIndex); + const leaderId = goalFormGroup?.get("responsible")?.value; + + if (!leaderId) return null; + + return collaborators.find(collab => collab.userId.toString() === leaderId.toString()); + } + + /** + * Обработчик изменения радио-кнопки для выбора лидера + * @param event - событие изменения + */ + public onLeaderRadioChange(event: Event): void { + const target = event.target as HTMLInputElement; + this.selectedLeaderId.set(target.value); + } + + /** + * Добавляет лидера на определенную цель + */ + public addLeaderToGoal(): void { + const goalIndex = this.activeGoalIndex(); + const leaderId = this.selectedLeaderId(); + + if (goalIndex === null || !leaderId) { + return; + } + + const goalFormGroup = this.goals.at(goalIndex); + goalFormGroup?.get("responsible")?.setValue(leaderId); + + this.closeGoalLeaderModal(); + } + + /** + * Открывает модальное окно выбора лидера для конкретной цели + * @param index - индекс цели + */ + public openGoalLeaderModal(index: number): void { + this.activeGoalIndex.set(index); + + const currentLeader = this.goals.at(index)?.get("responsible")?.value; + this.selectedLeaderId.set(currentLeader || ""); + + this.goalLeaderShowModal.set(true); + } + + /** + * Закрывает модальное окно выбора лидера + */ + public closeGoalLeaderModal(): void { + this.goalLeaderShowModal.set(false); + this.activeGoalIndex.set(null); + this.selectedLeaderId.set(""); + } + + /** + * Переключает состояние модального окна выбора лидера + * @param index - индекс цели (опционально) + */ + public toggleGoalLeaderModal(index?: number): void { + if (this.goalLeaderShowModal()) { + this.closeGoalLeaderModal(); + } else if (index !== undefined) { + this.openGoalLeaderModal(index); + } + } + /** * Сбрасывает все ошибки валидации во всех контролах FormArray цели. - * @param goals FormArray достижений */ - public clearAllGoalsErrors(goals: FormArray): void { + public clearAllGoalsErrors(): void { + const goals = this.goals; + goals.controls.forEach(control => { if (control instanceof FormGroup) { Object.keys(control.controls).forEach(key => { @@ -153,6 +273,79 @@ export class ProjectGoalService { }); } + /** + * Получает данные всех целей для отправки на сервер + * @returns массив объектов целей + */ + public getGoalsData(): any[] { + return this.goals.value.map((g: any) => ({ + id: g.id ?? null, + title: g.title, + completionDate: g.completionDate, + responsible: + g.responsible === null || g.responsible === undefined || g.responsible === "" + ? null + : Number(g.responsible), + isDone: !!g.isDone, + })); + } + + /** + * Сохраняет только новые цели (у которых id === null) — отправляет POST. + * После ответов присваивает полученные id в соответствующие FormGroup. + * Возвращает Observable массива результатов (в порядке отправки). + */ + public saveGoals(projectId: number) { + const goals = this.getGoalsData(); + + const newGoalsWithIndex = goals + .map((g: any, idx: number) => ({ g, idx })) + .filter(item => !item.g.id); + + if (newGoalsWithIndex.length === 0) { + return of([]); + } + + const requests = newGoalsWithIndex.map(item => { + const payload: GoalPostForm = { + title: item.g.title, + completionDate: item.g.completionDate, + responsible: item.g.responsible, + isDone: item.g.isDone, + }; + + return this.projectService.postGoals(projectId, payload).pipe( + map((res: any) => ({ res, idx: item.idx })), + catchError(err => of({ __error: true, err, original: item.g, idx: item.idx })) + ); + }); + + return forkJoin(requests).pipe( + tap(results => { + results.forEach((r: any) => { + if (r && r.__error) { + console.error("Failed to post goal", r.err, "original:", r.original); + return; + } + + const created = r.res; + const idx = r.idx; + + if (created && created.id !== undefined && created.id !== null) { + const formGroup = this.goals.at(idx); + if (formGroup) { + formGroup.get("id")?.setValue(created.id); + } + } else { + console.warn("postGoal response has no id field:", r.res); + } + }); + + this.syncGoalItems(this.goals); + }) + ); + } + /** * Сбрасывает состояние сервиса * Полезно при смене проекта или очистке формы @@ -160,5 +353,19 @@ export class ProjectGoalService { public reset(): void { this.goalItems.set([]); this.initialized = false; + this.closeGoalLeaderModal(); + } + + /** + * Очищает FormArray целей + */ + public clearGoalsFormArray(): void { + const goalFormArray = this.goals; + + while (goalFormArray.length !== 0) { + goalFormArray.removeAt(0); + } + + this.goalItems.set([]); } } 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 5ce1e7b50..8f872a7b1 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 @@ -259,7 +259,11 @@ }
-
+
diff --git a/projects/social_platform/src/app/office/shared/skills-basket/skills-basket.component.html b/projects/social_platform/src/app/office/shared/skills-basket/skills-basket.component.html index 6020c3638..13cd697c7 100644 --- a/projects/social_platform/src/app/office/shared/skills-basket/skills-basket.component.html +++ b/projects/social_platform/src/app/office/shared/skills-basket/skills-basket.component.html @@ -9,7 +9,7 @@ appIcon class="basket__skill-delete-icon" icon="basket" - appSquare="18" + appSquare="14" (click)="deleteSkill(skill.id)" >
diff --git a/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.html b/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.html index 051653088..f14e369bf 100644 --- a/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.html +++ b/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.html @@ -30,11 +30,11 @@

{{ vacancy.role }}

- + - +
diff --git a/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.scss b/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.scss index 84eff1bed..138d12730 100644 --- a/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.scss +++ b/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.scss @@ -41,19 +41,10 @@ display: flex; gap: 20px; margin-top: 10px; + align-items: center; app-button { flex-grow: 1; } } - - &__edit { - margin-top: 8px; - } - - i { - width: 24px; - height: 24px; - cursor: pointer; - } } diff --git a/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.html b/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.html index 48f551672..36f347af5 100644 --- a/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.html +++ b/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.html @@ -17,7 +17,7 @@ @if (fieldToDisplayMode === "chip" && value()) {
{{ $any(value())[fieldToDisplay] || value() }} - +
}
@@ -32,7 +32,13 @@ } @if (searchIcon && slimVersion) { } @if (searchIcon && !slimVersion) { - + }
@if (isOpen()) { diff --git a/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.scss b/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.scss index 365a8f0d8..1be8eb411 100644 --- a/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.scss +++ b/projects/social_platform/src/app/ui/components/autocomplete-input/autocomplete-input.component.scss @@ -15,7 +15,7 @@ padding: 12px; color: var(--black); background-color: var(--white); - border: 1px solid var(--gray); + border: 0.5px solid var(--gray); border-radius: var(--rounded-xxl); outline: none; transition: all 0.2s; diff --git a/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.html b/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.html index ae07d363e..1e3cf0453 100644 --- a/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.html +++ b/projects/social_platform/src/app/ui/components/avatar-control/avatar-control.component.html @@ -2,7 +2,7 @@

}

- } +
+ } @@ -179,7 +219,7 @@

@if (showProgramModalErrorMessage()) { -

+

{{ showProgramModalErrorMessage() }}

} diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.scss b/projects/social_platform/src/app/office/program/detail/main/main.component.scss index 099dd15de..cc49ec3d9 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.scss +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.scss @@ -205,34 +205,21 @@ &__body { position: relative; z-index: 2; - padding: 40px 24px 24px; - margin-top: -$body-slide; - border-radius: $body-slide; - - @include responsive.apply-desktop { - display: flex; - flex-wrap: wrap; - gap: 20px; - align-items: flex-end; - padding-top: 10px; - padding-left: 225px; - background-color: var(--white); - border: 1px solid var(--medium-grey-for-outline); - } + padding: 14px 0px 45px 0px; } &__avatar { position: absolute; bottom: $body-slide; - left: 50%; + left: 45%; z-index: 3; display: block; background-color: var(--white); border-radius: 50%; - transform: translateX(-50%) translateY(30px); + transform: translateX(-50%); + cursor: pointer; @include responsive.apply-desktop { - left: 35px; transform: translateY(50%); } } @@ -251,98 +238,20 @@ } &__actions { - display: flex; - flex-direction: column; - flex-grow: 1; - gap: 20px; - - @include responsive.apply-desktop { - flex-direction: row; - gap: 10px; - justify-content: flex-end; - - & > a { - flex-grow: 1; - flex-shrink: 0; - } - } - - app-button ::ng-deep .button--inline { - min-height: 38px; - } - } - - &__tag { - padding: 4px 10px; - color: var(--white); - background-color: var(--accent); - border-radius: 5px; - } - - &__location { display: flex; align-items: center; - - i { - margin-right: 5px; - } - } - - &__title { - margin-bottom: 10px; - color: var(--black); - text-align: center; - - @include typography.heading-4; - - @include responsive.apply-desktop { - text-align: unset; - - @include typography.heading-2; - } - } - - &__text { - flex-basis: 300px; - flex-grow: 9999; - color: var(--dark-grey); - } - - &__industry { - margin-right: 20px; - - @include responsive.apply-desktop { - margin-right: 40px; - } - } - - &__geo { - display: flex; - align-items: center; - - i { - margin-right: 5px; - } + gap: 20px; } &__presentation { display: block; - margin-top: 35px; - - @include responsive.apply-desktop { - margin-top: 0; - margin-left: auto; - } i { - margin-left: 10px; + margin-left: 15px; + color: var(--accent); } } - &__presentation-icon { - min-width: 16px; - } - &__edit { display: block; } diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.ts b/projects/social_platform/src/app/office/program/detail/main/main.component.ts index 60f92f0fc..913ed1f3e 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.ts +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.ts @@ -142,8 +142,6 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { }, }); - this.loadEvent = fromEvent(window, "load"); - this.subscriptions$().push(program$); this.subscriptions$().push(programIdSubscription$); this.subscriptions$().push(routeModalSub$); @@ -163,6 +161,8 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { .subscribe(); this.subscriptions$().push(scrollEvents$); } + + console.log(this.showDetails, this.program?.isUserMember); } ngOnDestroy(): void { @@ -276,10 +276,15 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { }); } - private loadEvent?: Observable; + onOpenDetailProgram(): void { + if (!this.program?.isUserMember) return; + + this.showDetails = true; + } program?: Program; registerDateExpired!: boolean; descriptionExpandable!: boolean; readFullDescription = false; + showDetails = false; } diff --git a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.html b/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.html index 154f19f0e..7089ccc7a 100644 --- a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.html +++ b/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.html @@ -1,7 +1,7 @@
- - + -->
- + @if (registerForm && schema) { @for (f of schema | keyvalue; track f.key) { diff --git a/projects/social_platform/src/app/office/program/list/main/main.component.html b/projects/social_platform/src/app/office/program/list/main/main.component.html index 8a576d980..80e29714f 100644 --- a/projects/social_platform/src/app/office/program/list/main/main.component.html +++ b/projects/social_platform/src/app/office/program/list/main/main.component.html @@ -10,4 +10,15 @@ }
} + +
+

фильтры

+ + + + +
diff --git a/projects/social_platform/src/app/office/program/list/main/main.component.scss b/projects/social_platform/src/app/office/program/list/main/main.component.scss index a34827eca..2fd40f7c6 100644 --- a/projects/social_platform/src/app/office/program/list/main/main.component.scss +++ b/projects/social_platform/src/app/office/program/list/main/main.component.scss @@ -1,7 +1,30 @@ .programs { + display: flex; + align-items: flex-start; + gap: 20px; + justify-content: space-between; + &__list { display: grid; - grid-template-columns: 1fr; + grid-template-columns: 330px 330px; + column-gap: 80px; + } + + &__filter { + display: flex; + flex-direction: column; gap: 20px; + flex-basis: 18%; + margin-top: 14px; + + &--block { + display: flex; + align-items: center; + gap: 12px; + } + + &--text { + color: var(--dark-grey); + } } } diff --git a/projects/social_platform/src/app/office/program/list/main/main.component.ts b/projects/social_platform/src/app/office/program/list/main/main.component.ts index 629116860..557c26be0 100644 --- a/projects/social_platform/src/app/office/program/list/main/main.component.ts +++ b/projects/social_platform/src/app/office/program/list/main/main.component.ts @@ -1,12 +1,15 @@ /** @format */ -import { Component, OnDestroy, OnInit } from "@angular/core"; +import { Component, OnDestroy, OnInit, signal } from "@angular/core"; import { ActivatedRoute, RouterLink } from "@angular/router"; import { map, Subscription } from "rxjs"; import { Program } from "@office/program/models/program.model"; import { ProgramCardComponent } from "../../shared/program-card/program-card.component"; import { NavService } from "@office/services/nav.service"; import Fuse from "fuse.js"; +import { CheckboxComponent, SelectComponent } from "@ui/components"; +import { generateOptionsList } from "@utils/generate-options-list"; +import { ClickOutsideModule } from "ng-click-outside"; /** * Главный компонент списка программ @@ -47,7 +50,13 @@ import Fuse from "fuse.js"; templateUrl: "./main.component.html", styleUrl: "./main.component.scss", standalone: true, - imports: [RouterLink, ProgramCardComponent], + imports: [ + RouterLink, + ProgramCardComponent, + CheckboxComponent, + SelectComponent, + ClickOutsideModule, + ], }) export class ProgramMainComponent implements OnInit, OnDestroy { constructor(private readonly route: ActivatedRoute, private readonly navService: NavService) {} @@ -58,6 +67,13 @@ export class ProgramMainComponent implements OnInit, OnDestroy { searchedPrograms: Program[] = []; subscriptions$: Subscription[] = []; + readonly programOptionsFilter = generateOptionsList(4, "strings", [ + "все", + "актуальные", + "архив", + "учавстсвовал", + ]); + ngOnInit(): void { this.navService.setNavTitle("Программы"); @@ -80,6 +96,15 @@ export class ProgramMainComponent implements OnInit, OnDestroy { programs$ && this.subscriptions$.push(programs$); } + isPparticipating = signal(false); + + /** + * Переключает состояние чекбокса + */ + onTogglePparticipating(): void { + this.isPparticipating.set(!this.isPparticipating()); + } + ngOnDestroy(): void { this.subscriptions$.forEach($ => $?.unsubscribe()); } 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 f9239900c..d03bf2eac 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 @@ -52,8 +52,8 @@ export class Program { viewsCount!: number; likesCount!: number; isUserLiked!: boolean; - isUserManager?: boolean; - isUserMember?: boolean; + isUserManager!: boolean; + isUserMember!: boolean; static default(): Program { return { diff --git a/projects/social_platform/src/app/office/program/program.component.html b/projects/social_platform/src/app/office/program/program.component.html index 74d3eb085..92428d0c2 100644 --- a/projects/social_platform/src/app/office/program/program.component.html +++ b/projects/social_platform/src/app/office/program/program.component.html @@ -1,33 +1,12 @@ - -
- - }, - ]" - > -
- - - - - -
-
+
diff --git a/projects/social_platform/src/app/office/program/program.component.scss b/projects/social_platform/src/app/office/program/program.component.scss index b32d5339e..ca045dcbe 100644 --- a/projects/social_platform/src/app/office/program/program.component.scss +++ b/projects/social_platform/src/app/office/program/program.component.scss @@ -7,30 +7,3 @@ margin-bottom: 12px; } } - -:host ::ng-deep { - .bar__right { - display: flex; - align-items: center; - margin-left: auto; - } - - .bar__add-project { - display: flex; - align-items: center; - cursor: pointer; - - i { - display: block; - margin-left: 6px; - } - } - - .bar__add-project-text { - display: none; - - @include responsive.apply-desktop { - display: inline; - } - } -} diff --git a/projects/social_platform/src/app/office/program/program.component.ts b/projects/social_platform/src/app/office/program/program.component.ts index fcbea3c83..482501c31 100644 --- a/projects/social_platform/src/app/office/program/program.component.ts +++ b/projects/social_platform/src/app/office/program/program.component.ts @@ -8,6 +8,7 @@ import { FormBuilder, FormGroup, ReactiveFormsModule } from "@angular/forms"; import { SearchComponent } from "@ui/components/search/search.component"; import { BarComponent } from "@ui/components"; import { ProgramService } from "./services/program.service"; +import { BackComponent } from "@uilib"; /** * Основной компонент модуля "Программы" @@ -35,7 +36,7 @@ import { ProgramService } from "./services/program.service"; templateUrl: "./program.component.html", styleUrl: "./program.component.scss", standalone: true, - imports: [ReactiveFormsModule, SearchComponent, RouterOutlet, BarComponent], + imports: [ReactiveFormsModule, SearchComponent, RouterOutlet, BarComponent, BackComponent], }) export class ProgramComponent implements OnInit, OnDestroy { constructor( 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 e17d4aec4..c0c094c1d 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 @@ -2,29 +2,35 @@ @if (program) {
-
-
-

{{ program.name }}

-
- +
+
-

{{ program.shortDescription }}

-

- Регистрация открыта до: - {{ program.datetimeRegistrationEnds | date: "dd MMMM yyyy" }} -

-
-

- Дата проведения: {{ program.datetimeStarted | date: "dd MMMM yyyy" }} -

-

-

-

- {{ program.datetimeFinished | date: "dd MMMM yyyy" }} + +

+

{{ program.name }}

+ +

+ Регистрация до: + {{ program.datetimeRegistrationEnds | date: "dd MMMM" }}

-
-
-
- {{ program.viewsCount }} + +
+

+ {{ program.datetimeStarted | date: "dd.MM.yyyy" }} +

+

-

+

+ {{ program.datetimeFinished | date: "dd.MM.yyyy" }} +

+ +

• для всей России

diff --git a/projects/social_platform/src/app/office/program/shared/program-card/program-card.component.scss b/projects/social_platform/src/app/office/program/shared/program-card/program-card.component.scss index 9cbbfd983..4ebb5d137 100644 --- a/projects/social_platform/src/app/office/program/shared/program-card/program-card.component.scss +++ b/projects/social_platform/src/app/office/program/shared/program-card/program-card.component.scss @@ -4,75 +4,60 @@ .card { position: relative; - padding: 24px 15px; - background-color: var(--white); - border-radius: var(--rounded-xl); - - @include responsive.apply-desktop { - border: 1px solid var(--medium-grey-for-outline); - } - - &__head { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 15px; - } - - &__basket { - color: var(--red); - } - - &__draft { - color: var(--red); + display: flex; + gap: 20px; + align-items: center; + margin-top: 25px; + + &__photo { + position: relative; + border-radius: 50%; + border: 0.5px solid var(--dark-grey); + padding: 12px; + z-index: 10; + background: var(--white); + } + + &__info { + border: 0.5px solid var(--dark-grey); + border-radius: var(--rounded-lg); + padding: 22px 22px 22px 50px; + position: absolute; + left: 17%; + width: 100%; + z-index: 1; } &__name { - margin-bottom: 3px; color: var(--black); } - &__media { - display: flex; - margin-top: 16px; - color: var(--dark-grey); - } - - &__views { - display: flex; - align-items: center; - margin-left: auto; - - i { - margin-right: 5px; - } - } - &__dates { display: flex; gap: 5px; } - &__industry { + &__description { color: var(--dark-grey); } - &__description { - color: var(--dark-grey); + &__registration { + margin-top: 2px; + margin-bottom: 6px; + padding: 2px 2px 2px 30px; + color: var(--white); + border-radius: var(--rounded-lg); - &:not(:last-child) { - margin-bottom: 16px; + &--actual { + background-color: var(--accent); } - } - &__bottom { - display: flex; - align-items: center; - justify-content: space-between; - margin-top: 16px; + &--ended { + background-color: var(--red); + } - &:empty { - margin-top: 0; + &--participating { + background-color: var(--green); } } } diff --git a/projects/social_platform/src/app/office/program/shared/program-card/program-card.component.ts b/projects/social_platform/src/app/office/program/shared/program-card/program-card.component.ts index b83f3797f..2cb0f28fd 100644 --- a/projects/social_platform/src/app/office/program/shared/program-card/program-card.component.ts +++ b/projects/social_platform/src/app/office/program/shared/program-card/program-card.component.ts @@ -4,7 +4,7 @@ import { Component, Input, OnInit } from "@angular/core"; import { Program } from "@office/program/models/program.model"; import { IconComponent } from "@ui/components"; import { AvatarComponent } from "@ui/components/avatar/avatar.component"; -import { DatePipe } from "@angular/common"; +import { DatePipe, NgClass } from "@angular/common"; /** * Компонент карточки программы @@ -36,7 +36,7 @@ import { DatePipe } from "@angular/common"; templateUrl: "./program-card.component.html", styleUrl: "./program-card.component.scss", standalone: true, - imports: [AvatarComponent, IconComponent, DatePipe], + imports: [AvatarComponent, IconComponent, DatePipe, NgClass], }) export class ProgramCardComponent implements OnInit { constructor() {} @@ -44,4 +44,7 @@ export class ProgramCardComponent implements OnInit { @Input({ required: true }) program?: Program; ngOnInit(): void {} + + // TODO: изменить когда у нас появятся в информации о программе состояние о завершенности/участии + type = "actual"; } diff --git a/projects/social_platform/src/app/office/projects/detail/detail.component.html b/projects/social_platform/src/app/office/projects/detail/detail.component.html index cfd101ee5..810cb2618 100644 --- a/projects/social_platform/src/app/office/projects/detail/detail.component.html +++ b/projects/social_platform/src/app/office/projects/detail/detail.component.html @@ -2,7 +2,7 @@ @if (project) {
- + > -->
diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.scss index e01771c82..6f8ce9eb4 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.scss @@ -86,7 +86,7 @@ color: var(--black) !important; &--placeholder { - color: var(--grey-for-text) !important; + color: var(--dark-grey) !important; } @include typography.body-10; diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.ts b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.ts index abf72ab11..2c85f33db 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.ts +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.ts @@ -31,15 +31,14 @@ import { AsyncPipe, CommonModule } from "@angular/common"; import { ControlErrorPipe } from "@corelib"; import { ProjectFormService } from "../../services/project-form.service"; import { IconComponent } from "@uilib"; -import { generateTrlList } from "@utils/generate-trl-list"; import { ProjectContactsService } from "../../services/project-contacts.service"; import { ProjectGoalService } from "../../services/project-goals.service"; import { ModalComponent } from "@ui/components/modal/modal.component"; import { ProjectTeamService } from "../../services/project-team.service"; import { AvatarComponent } from "@ui/components/avatar/avatar.component"; import { ProjectService } from "@office/services/project.service"; -import { Goal } from "@office/models/goals.model"; import { RouterLink } from "@angular/router"; +import { generateOptionsList } from "@utils/generate-options-list"; @Component({ selector: "app-project-main-step", @@ -88,7 +87,7 @@ export class ProjectMainStepComponent implements OnInit, OnDestroy { readonly errorMessage = ErrorMessage; readonly trackList = trackProjectList; readonly directionList = directionProjectList; - readonly trlList = generateTrlList(9); + readonly trlList = generateOptionsList(9, "numbers"); goalLeaderShowModal = false; activeGoalIndex = signal(null); diff --git a/projects/social_platform/src/app/office/projects/projects.component.html b/projects/social_platform/src/app/office/projects/projects.component.html index 73383a85a..67f47dc22 100644 --- a/projects/social_platform/src/app/office/projects/projects.component.html +++ b/projects/social_platform/src/app/office/projects/projects.component.html @@ -3,7 +3,7 @@
@if (projectService.projectsCount$ | async; as count) { - - + -->
@if (isMy) { Черновик

} @if (canDelete) { - + }
diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss b/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss index 1fcc12526..7bc5f6524 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss +++ b/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss @@ -59,7 +59,7 @@ &__basket { margin-left: auto; - color: var(--red); + color: var(--red) !important; } &__draft { diff --git a/projects/social_platform/src/app/office/vacancies/detail/vacancies-detail.component.html b/projects/social_platform/src/app/office/vacancies/detail/vacancies-detail.component.html index 8cbd22285..61917f96a 100644 --- a/projects/social_platform/src/app/office/vacancies/detail/vacancies-detail.component.html +++ b/projects/social_platform/src/app/office/vacancies/detail/vacancies-detail.component.html @@ -2,7 +2,7 @@ @if (vacancy) {
- +
diff --git a/projects/social_platform/src/app/office/vacancies/vacancies.component.html b/projects/social_platform/src/app/office/vacancies/vacancies.component.html index ae96e6525..9d8b69aa8 100644 --- a/projects/social_platform/src/app/office/vacancies/vacancies.component.html +++ b/projects/social_platform/src/app/office/vacancies/vacancies.component.html @@ -1,6 +1,6 @@ - +> --> diff --git a/projects/social_platform/src/app/office/vacancy/send/send.component.html b/projects/social_platform/src/app/office/vacancy/send/send.component.html index b1d419ab7..cda720aa6 100644 --- a/projects/social_platform/src/app/office/vacancy/send/send.component.html +++ b/projects/social_platform/src/app/office/vacancy/send/send.component.html @@ -1,6 +1,6 @@
- + @if (authService.profile | async; as profile) {
diff --git a/projects/social_platform/src/app/office/vacancy/send/send.component.ts b/projects/social_platform/src/app/office/vacancy/send/send.component.ts index 6ddb7778b..4904b9ab7 100644 --- a/projects/social_platform/src/app/office/vacancy/send/send.component.ts +++ b/projects/social_platform/src/app/office/vacancy/send/send.component.ts @@ -17,6 +17,7 @@ import { ModalComponent } from "@ui/components/modal/modal.component"; import { AsyncPipe } from "@angular/common"; import { UploadFileComponent } from "@ui/components/upload-file/upload-file.component"; import { noteList } from "projects/core/src/consts/note-list"; +import { BackComponent } from "@uilib"; /** * Компонент для отправки отклика на вакансию @@ -41,6 +42,7 @@ import { noteList } from "projects/core/src/consts/note-list"; UserRolePipe, BarComponent, UploadFileComponent, + BackComponent, ], }) export class VacancySendComponent implements OnInit { diff --git a/projects/social_platform/src/app/ui/components/avatar/avatar.component.html b/projects/social_platform/src/app/ui/components/avatar/avatar.component.html index 3f4365743..dabb99024 100644 --- a/projects/social_platform/src/app/ui/components/avatar/avatar.component.html +++ b/projects/social_platform/src/app/ui/components/avatar/avatar.component.html @@ -1,34 +1,33 @@ -
- @if (progress) { -
- } +
+
+ @if (progress) { +
+ } - avatar - @if (isOnline) { -
- } + avatar + + @if (isOnline) { +
+ } +
diff --git a/projects/social_platform/src/app/ui/components/avatar/avatar.component.scss b/projects/social_platform/src/app/ui/components/avatar/avatar.component.scss index c9e24f0f1..7402f9b4c 100644 --- a/projects/social_platform/src/app/ui/components/avatar/avatar.component.scss +++ b/projects/social_platform/src/app/ui/components/avatar/avatar.component.scss @@ -46,7 +46,8 @@ &--border { img { - border: 2px solid var(--white); + border-width: 0.5px; + border-style: solid; } } } diff --git a/projects/social_platform/src/app/ui/components/avatar/avatar.component.ts b/projects/social_platform/src/app/ui/components/avatar/avatar.component.ts index c36ed136a..1a499949e 100644 --- a/projects/social_platform/src/app/ui/components/avatar/avatar.component.ts +++ b/projects/social_platform/src/app/ui/components/avatar/avatar.component.ts @@ -37,6 +37,8 @@ export class AvatarComponent implements OnInit { /** Отображать рамку */ @Input() hasBorder = false; + @Input() borderColor: "dark-grey" | "white" | "black" | "accent" = "white"; + /** Показывать индикатор онлайн статуса */ @Input() isOnline = false; diff --git a/projects/social_platform/src/app/ui/components/bar/bar.component.html b/projects/social_platform/src/app/ui/components/bar/bar.component.html index 87f531019..217a389f4 100644 --- a/projects/social_platform/src/app/ui/components/bar/bar.component.html +++ b/projects/social_platform/src/app/ui/components/bar/bar.component.html @@ -2,9 +2,6 @@
diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.scss b/projects/social_platform/src/app/office/program/detail/main/main.component.scss index e139d74f6..010ed1340 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.scss +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.scss @@ -11,26 +11,9 @@ &__main { display: grid; grid-template-columns: 1fr; - row-gap: 8px; - align-items: start; - - @include responsive.apply-desktop { - grid-template-columns: 2fr 1fr; - grid-gap: 16px; - } - } - - &__aside { - display: grid; - grid-row-start: 3; - gap: 20px; - - @include responsive.apply-desktop { - grid-row-start: unset; - } } - &__left { + &__right { display: flex; flex-direction: column; gap: 20px; @@ -39,8 +22,8 @@ &__section { padding: 24px; background-color: var(--white); - border: 1px solid var(--medium-grey-for-outline); - border-radius: var(--rounded-xl); + border: 0.5px solid var(--medium-grey-for-outline); + border-radius: var(--rounded-lg); } &__info { @@ -57,10 +40,6 @@ } } - &__about { - padding: 0 24px; - } - &__news { grid-row-start: 4; @@ -77,18 +56,10 @@ } } - &__views-desktop { - display: none; - color: var(--dark-grey); - - @include responsive.apply-desktop { - display: flex; - align-items: center; - } - - i { - margin-right: 5px; - } + &__details { + display: grid; + grid-gap: 20px; + grid-template-columns: 2fr 5fr 3fr; } } @@ -108,35 +79,25 @@ } .about { + background-color: var(--light-white); + border-radius: var(--rounded-lg); + padding: 24px; + &__head { display: flex; align-items: center; justify-content: space-between; + border-bottom: 0.5px solid var(--accent); + margin-bottom: 8px; - @include responsive.apply-desktop { - display: block; + &--icon { + color: var(--accent); } } &__title { - margin-bottom: 12px; - color: var(--black); - } - - &__views { - display: flex; - align-items: center; - color: var(--gray); - - @include typography.body-12; - - @include responsive.apply-desktop { - display: none; - } - - i { - margin-right: 5px; - } + margin-bottom: 8px; + color: var(--accent); } /* stylelint-disable value-no-vendor-prefix */ @@ -170,7 +131,7 @@ /* stylelint-enable value-no-vendor-prefix */ &__read-full { - margin-top: 2px; + margin-top: 8px; color: var(--accent); cursor: pointer; } @@ -241,6 +202,7 @@ display: flex; gap: 20px; align-items: center; + justify-content: space-between; } &__presentation { @@ -258,7 +220,7 @@ } .read-more { - margin-top: 12px; + margin-top: 8px; color: var(--accent); cursor: pointer; transition: background-color 0.2s; @@ -271,22 +233,32 @@ .links { overflow: hidden; + &__section { + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 0.5px solid var(--accent); + margin-bottom: 8px; + } + + &__icon { + color: var(--accent); + } + &__title { - margin-bottom: 12px; + margin-bottom: 8px; + color: var(--accent); } &__item { - &:not(:last-child) { - margin-bottom: 3px; - - @include responsive.apply-desktop { - margin-bottom: 12px; - } - } + cursor: pointer; } ul { overflow: hidden; + display: flex; + flex-direction: column; + gap: 8px; span { overflow: hidden; diff --git a/projects/social_platform/src/app/office/shared/news-form/news-form.component.html b/projects/social_platform/src/app/office/shared/news-form/news-form.component.html index 419382c0a..978b24c76 100644 --- a/projects/social_platform/src/app/office/shared/news-form/news-form.component.html +++ b/projects/social_platform/src/app/office/shared/news-form/news-form.component.html @@ -2,18 +2,17 @@
- - + > +
@for (i of imagesList; track i.id) { @@ -40,7 +39,7 @@
diff --git a/projects/social_platform/src/app/office/shared/news-form/news-form.component.scss b/projects/social_platform/src/app/office/shared/news-form/news-form.component.scss index 64b894d34..f229812e9 100644 --- a/projects/social_platform/src/app/office/shared/news-form/news-form.component.scss +++ b/projects/social_platform/src/app/office/shared/news-form/news-form.component.scss @@ -1,11 +1,21 @@ .form { - padding: 16px 20px; - background-color: var(--white); - border: 1px solid var(--medium-grey-for-outline); - border-radius: var(--rounded-xl); + padding: 20px 12px 10px; + background-color: var(--light-white); + border: 0.5px solid var(--medium-grey-for-outline); + border-radius: var(--rounded-lg); &__row { display: flex; + align-items: center; + border-bottom: 0.5px solid var(--medium-grey-for-outline); + + ::ng-deep app-input { + input { + width: 100%; + padding: 12px 105px 6px 0px; + background-color: transparent; + } + } } &__files { @@ -18,30 +28,18 @@ } } - &__textarea { - width: 100%; - padding-bottom: 10px; - margin-right: 20px; + &__send { + color: var(--accent); + margin-left: 20px; + cursor: pointer; + } + + &__input { resize: none; border: none; outline: none; - } - - &__submit { - display: flex; - align-items: center; - justify-content: center; - width: 36px; - height: 36px; - color: var(--white); - cursor: pointer; - background-color: var(--accent); - border-radius: 8px; - transition: background-color 0.2s; - - &:hover { - background-color: var(--accent-dark); - } + background: transparent; + flex-grow: 1; } &__images { @@ -53,8 +51,6 @@ .footer { padding-top: 10px; - margin-top: 20px; - border-top: 1px solid var(--medium-grey-for-outline); &__attach { input { @@ -63,6 +59,7 @@ i { cursor: pointer; + color: var(--dark-grey); } } } diff --git a/projects/social_platform/src/app/office/shared/news-form/news-form.component.ts b/projects/social_platform/src/app/office/shared/news-form/news-form.component.ts index 988d1c1c0..59feecd6d 100644 --- a/projects/social_platform/src/app/office/shared/news-form/news-form.component.ts +++ b/projects/social_platform/src/app/office/shared/news-form/news-form.component.ts @@ -8,8 +8,9 @@ import { FileService } from "@core/services/file.service"; import { forkJoin, noop, Observable, tap } from "rxjs"; import { FileUploadItemComponent } from "@ui/components/file-upload-item/file-upload-item.component"; import { ImgCardComponent } from "../img-card/img-card.component"; -import { IconComponent } from "@ui/components"; +import { IconComponent, ButtonComponent, InputComponent } from "@ui/components"; import { AutosizeModule } from "ngx-autosize"; +import { TextareaComponent } from "@ui/components/textarea/textarea.component"; /** * Компонент формы создания новости @@ -41,6 +42,9 @@ import { AutosizeModule } from "ngx-autosize"; IconComponent, ImgCardComponent, FileUploadItemComponent, + ButtonComponent, + TextareaComponent, + InputComponent, ], }) export class NewsFormComponent implements OnInit { diff --git a/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.html b/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.html index f14e369bf..4301490d7 100644 --- a/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.html +++ b/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.html @@ -9,17 +9,14 @@

{{ vacancy.role }}

@if (vacancy.requiredSkills.length) { @for (skill of vacancy.requiredSkills.slice(0, 5); track $index) { -

- {{ skill.name }} -

+ {{ + skill.name + }} @if (vacancy.specialization) { -

- {{ vacancy.specialization ? vacancy.specialization : "" }} -

+ {{ + vacancy.specialization ? vacancy.specialization : "" + }} } } @if (vacancy.requiredSkills.length > 5) {

+ {{ vacancy.requiredSkills.length - 5 }} diff --git a/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.ts b/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.ts index aa8cea837..76adbc308 100644 --- a/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.ts +++ b/projects/social_platform/src/app/office/shared/vacancy-card/vacancy-card.component.ts @@ -3,6 +3,7 @@ import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { Vacancy } from "@models/vacancy.model"; import { IconComponent, ButtonComponent } from "@ui/components"; +import { TagComponent } from "@ui/components/tag/tag.component"; /** * Компонент карточки вакансии @@ -29,7 +30,7 @@ import { IconComponent, ButtonComponent } from "@ui/components"; templateUrl: "./vacancy-card.component.html", styleUrl: "./vacancy-card.component.scss", standalone: true, - imports: [IconComponent, ButtonComponent], + imports: [IconComponent, ButtonComponent, TagComponent], }) export class VacancyCardComponent implements OnInit { constructor() {} diff --git a/projects/social_platform/src/app/ui/components/input/input.component.html b/projects/social_platform/src/app/ui/components/input/input.component.html index b1b4b1686..b848a5995 100644 --- a/projects/social_platform/src/app/ui/components/input/input.component.html +++ b/projects/social_platform/src/app/ui/components/input/input.component.html @@ -30,6 +30,7 @@ [class.field__input--big]="size === 'big'" [class.field__input--error]="error" [class.field__input--with-tooltip]="size !== 'small'" + [class.field__input--border]="hasBorder" /> } @if (error) {

diff --git a/projects/social_platform/src/app/ui/components/tag/tag.component.scss b/projects/social_platform/src/app/ui/components/tag/tag.component.scss index 02612805f..344c15862 100644 --- a/projects/social_platform/src/app/ui/components/tag/tag.component.scss +++ b/projects/social_platform/src/app/ui/components/tag/tag.component.scss @@ -3,16 +3,22 @@ .tag { display: flex; align-items: center; + padding: 5px 40px; + border-radius: var(--rounded-xxl); max-width: 300px; - padding: 5px 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - border-radius: var(--rounded-md); + color: var(--accent); &--primary { - color: var(--dark-grey); - background-color: var(--grey-button); + color: var(--white); + background-color: var(--accent); + } + + &--secondary { + background: transparent; + border: 0.5px solid var(--accent); } &--accent { diff --git a/projects/social_platform/src/app/ui/components/tag/tag.component.ts b/projects/social_platform/src/app/ui/components/tag/tag.component.ts index 76a38c30c..6c53a90a1 100644 --- a/projects/social_platform/src/app/ui/components/tag/tag.component.ts +++ b/projects/social_platform/src/app/ui/components/tag/tag.component.ts @@ -25,7 +25,7 @@ export class TagComponent implements OnInit { constructor() {} /** Цветовая схема тега */ - @Input() color: "primary" | "accent" | "complete" = "primary"; + @Input() color: "primary" | "secondary" | "accent" | "complete" = "primary"; ngOnInit(): void {} } diff --git a/projects/social_platform/src/assets/icons/svg/phone.svg b/projects/social_platform/src/assets/icons/svg/phone.svg new file mode 100644 index 000000000..265e26517 --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/phone.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/svg/send.svg b/projects/social_platform/src/assets/icons/svg/send.svg index 47674145a..62fb67c30 100644 --- a/projects/social_platform/src/assets/icons/svg/send.svg +++ b/projects/social_platform/src/assets/icons/svg/send.svg @@ -1,3 +1,3 @@ - - + + diff --git a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg index 559284079..7b4a264c5 100644 --- a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg +++ b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/projects/social_platform/src/styles/_colors.scss b/projects/social_platform/src/styles/_colors.scss index a5a38999c..a52891b1d 100644 --- a/projects/social_platform/src/styles/_colors.scss +++ b/projects/social_platform/src/styles/_colors.scss @@ -16,6 +16,7 @@ --gold-dark: #f7cf4d; // GRAY + --light-white: #fff; --white: #fafafa; --black: #333; --dark-grey: #a59fb9; diff --git a/projects/social_platform/src/styles/components/_contact-link.scss b/projects/social_platform/src/styles/components/_contact-link.scss index f66393715..b541e39bc 100644 --- a/projects/social_platform/src/styles/components/_contact-link.scss +++ b/projects/social_platform/src/styles/components/_contact-link.scss @@ -1,6 +1,7 @@ .contact-link { color: var(--accent); transition: color 0.2s; + cursor: pointer; &:hover { color: var(--accent-dark); @@ -9,6 +10,7 @@ &__link { display: flex; align-items: center; + cursor: pointer; } &__icon { @@ -16,10 +18,10 @@ flex-shrink: 0; align-items: center; justify-content: center; - width: 26px; - height: 26px; - margin-right: 12px; - border: 1px solid var(--medium-grey-for-outline); - border-radius: 6px; + width: 20px; + height: 20px; + margin-right: 6px; + border: 0.5px solid var(--medium-grey-for-outline); + border-radius: var(--rounded-xxl); } } From 2ef1e8dba97be79f972b5d99a0f366201649e441 Mon Sep 17 00:00:00 2001 From: Awakich Date: Thu, 11 Sep 2025 01:16:14 +0300 Subject: [PATCH 048/126] change styles for news form component & detail program page --- .../office/program/detail/main/main.component.scss | 12 ++++++------ .../office/shared/news-form/news-form.component.scss | 10 +++++----- .../src/app/ui/components/tag/tag.component.scss | 6 +++--- .../src/styles/components/_contact-link.scss | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.scss b/projects/social_platform/src/app/office/program/detail/main/main.component.scss index 010ed1340..499f08119 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.scss +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.scss @@ -58,8 +58,8 @@ &__details { display: grid; - grid-gap: 20px; grid-template-columns: 2fr 5fr 3fr; + grid-gap: 20px; } } @@ -79,16 +79,16 @@ } .about { + padding: 24px; background-color: var(--light-white); border-radius: var(--rounded-lg); - padding: 24px; &__head { display: flex; align-items: center; justify-content: space-between; - border-bottom: 0.5px solid var(--accent); margin-bottom: 8px; + border-bottom: 0.5px solid var(--accent); &--icon { color: var(--accent); @@ -235,10 +235,10 @@ &__section { display: flex; - justify-content: space-between; align-items: center; - border-bottom: 0.5px solid var(--accent); + justify-content: space-between; margin-bottom: 8px; + border-bottom: 0.5px solid var(--accent); } &__icon { @@ -255,10 +255,10 @@ } ul { - overflow: hidden; display: flex; flex-direction: column; gap: 8px; + overflow: hidden; span { overflow: hidden; diff --git a/projects/social_platform/src/app/office/shared/news-form/news-form.component.scss b/projects/social_platform/src/app/office/shared/news-form/news-form.component.scss index f229812e9..7b3794764 100644 --- a/projects/social_platform/src/app/office/shared/news-form/news-form.component.scss +++ b/projects/social_platform/src/app/office/shared/news-form/news-form.component.scss @@ -12,7 +12,7 @@ ::ng-deep app-input { input { width: 100%; - padding: 12px 105px 6px 0px; + padding: 12px 105px 6px 0; background-color: transparent; } } @@ -29,17 +29,17 @@ } &__send { - color: var(--accent); margin-left: 20px; + color: var(--accent); cursor: pointer; } &__input { + flex-grow: 1; resize: none; + background: transparent; border: none; outline: none; - background: transparent; - flex-grow: 1; } &__images { @@ -58,8 +58,8 @@ } i { - cursor: pointer; color: var(--dark-grey); + cursor: pointer; } } } diff --git a/projects/social_platform/src/app/ui/components/tag/tag.component.scss b/projects/social_platform/src/app/ui/components/tag/tag.component.scss index 344c15862..9c307d574 100644 --- a/projects/social_platform/src/app/ui/components/tag/tag.component.scss +++ b/projects/social_platform/src/app/ui/components/tag/tag.component.scss @@ -3,13 +3,13 @@ .tag { display: flex; align-items: center; - padding: 5px 40px; - border-radius: var(--rounded-xxl); max-width: 300px; + padding: 5px 40px; overflow: hidden; + color: var(--accent); text-overflow: ellipsis; white-space: nowrap; - color: var(--accent); + border-radius: var(--rounded-xxl); &--primary { color: var(--white); diff --git a/projects/social_platform/src/styles/components/_contact-link.scss b/projects/social_platform/src/styles/components/_contact-link.scss index b541e39bc..758108b58 100644 --- a/projects/social_platform/src/styles/components/_contact-link.scss +++ b/projects/social_platform/src/styles/components/_contact-link.scss @@ -1,7 +1,7 @@ .contact-link { color: var(--accent); - transition: color 0.2s; cursor: pointer; + transition: color 0.2s; &:hover { color: var(--accent-dark); From 88932700cc10e0773f8c1cfef9f92906b8882f30 Mon Sep 17 00:00:00 2001 From: Awakich Date: Thu, 11 Sep 2025 23:29:21 +0300 Subject: [PATCH 049/126] change detail program page for manager & member & add modal for adding project to program --- .../chat-direct/chat-direct.component.html | 2 +- .../src/app/office/chat/chat.component.html | 4 +- .../detail/profile-detail.component.html | 6 +- .../program/detail/main/main.component.html | 155 ++++++++++++------ .../program/detail/main/main.component.scss | 101 +++++++++++- .../program/detail/main/main.component.ts | 70 +++++++- .../rate-projects.component.html | 4 +- .../detail/register/register.component.html | 2 +- .../shared/news-card/news-card.component.html | 29 ++-- .../shared/news-card/news-card.component.scss | 24 +-- .../src/app/office/program/program.routes.ts | 7 - .../projects/detail/detail.component.html | 4 +- .../project-main-step.component.html | 2 +- .../office/projects/projects.component.html | 4 +- .../shared/news-form/news-form.component.html | 2 +- .../detail/vacancies-detail.component.html | 2 +- .../office/vacancies/vacancies.component.html | 4 +- .../office/vacancy/send/send.component.html | 2 +- .../app/ui/components/bar/bar.component.html | 3 + .../app/ui/components/bar/bar.component.ts | 9 +- .../components/button/button.component.html | 1 + .../components/button/button.component.scss | 7 + .../ui/components/button/button.component.ts | 2 +- .../ui/components/input/input.component.scss | 4 +- .../src/assets/icons/svg/lock.svg | 3 + .../assets/icons/symbol/svg/sprite.css.svg | 2 +- .../src/styles/_typography.scss | 13 ++ 27 files changed, 340 insertions(+), 128 deletions(-) create mode 100644 projects/social_platform/src/assets/icons/svg/lock.svg diff --git a/projects/social_platform/src/app/office/chat/chat-direct/chat-direct/chat-direct.component.html b/projects/social_platform/src/app/office/chat/chat-direct/chat-direct/chat-direct.component.html index d2abb0b4e..d93b657f2 100644 --- a/projects/social_platform/src/app/office/chat/chat-direct/chat-direct/chat-direct.component.html +++ b/projects/social_platform/src/app/office/chat/chat-direct/chat-direct/chat-direct.component.html @@ -1,7 +1,7 @@
- +
@if (chat) { diff --git a/projects/social_platform/src/app/office/chat/chat.component.html b/projects/social_platform/src/app/office/chat/chat.component.html index 6aab0d22f..de236f37c 100644 --- a/projects/social_platform/src/app/office/chat/chat.component.html +++ b/projects/social_platform/src/app/office/chat/chat.component.html @@ -2,7 +2,7 @@
- + >
diff --git a/projects/social_platform/src/app/office/profile/detail/profile-detail.component.html b/projects/social_platform/src/app/office/profile/detail/profile-detail.component.html index 61d1db3a6..0c06a74d5 100644 --- a/projects/social_platform/src/app/office/profile/detail/profile-detail.component.html +++ b/projects/social_platform/src/app/office/profile/detail/profile-detail.component.html @@ -4,7 +4,7 @@
@if (loggedUserId$ | async; as loggedUserId) { - + > } @else { - + }
diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.html b/projects/social_platform/src/app/office/program/detail/main/main.component.html index 775e825a5..831767ece 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.html +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.html @@ -1,28 +1,5 @@ - - @if (program) {
@@ -49,28 +26,34 @@
- @if (!program.isUserMember && !registerDateExpired) { @if + @if (!program.isUserMember && !program.isUserManager && !registerDateExpired) { @if (program.name.includes("Кейс-чемпионат MIR")) { - Зарегистрироваться + + зарегистрироваться + } @else { - Зарегистрироваться + + зарегистрироваться + - } } @else if (program.isUserMember || program.isUserManager) { + } } @else if (program.isUserMember) { подать проект + } @else if (program.isUserManager) { + + + оценка проектов + + } + @if (!program.isUserManager) { узнать подробнее - + } @else { + + + проекты-участники + + + } @if (!program.isUserManager) { информация с ссылок + } @else { + + + участники + + + }
- @if (!program.isUserMember && !showDetails) { + @if ((!program.isUserMember || program.isUserManager) && !showDetails) {
- } @else if (program.isUserMember && showDetails) { + } @else if ((program.isUserMember && showDetails) || program.isUserManager) {
} @@ -255,4 +253,55 @@ >
+ + +
+
+ +

выберите проект для подачи

+

+ после выбора проекта будет создан дубликат данного проекта для заполнения под конкретный + конкурс +

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

    + {{ project.name }} +

    +
    + +
  • + } +
+
+
+ + + выбрать проект + +
+
} diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.scss b/projects/social_platform/src/app/office/program/detail/main/main.component.scss index 499f08119..a648f4cbd 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.scss +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.scss @@ -61,6 +61,13 @@ grid-template-columns: 2fr 5fr 3fr; grid-gap: 20px; } + + &__analytics { + padding: 12px; + border-radius: var(--rounded-lg); + border: 0.5px solid var(--medium-grey-for-outline); + background-color: var(--light-white); + } } .bar__add-project { @@ -78,6 +85,88 @@ display: inline; } +.project { + margin-top: 40px; + + &__list { + display: flex; + flex-direction: column; + gap: 10px; + align-items: center; + + max-height: none; + + &--scrollable { + max-height: 180px; + overflow-y: auto; + } + } + + &__item { + display: flex; + flex-grow: 1; + align-items: center; + justify-content: space-between; + width: 300px; + margin-bottom: 12px; + + &--info { + display: flex; + gap: 20px; + align-items: center; + } + } +} + +.analytics { + text-align: center; + + &__soon { + position: relative; + border-radius: var(--rounded-lg); + border: 0.5px solid var(--medium-grey-for-outline); + margin-bottom: 4px; + } + + &__closed { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 2px; + padding: 24px 37px; + + &--text { + color: var(--dark-grey); + } + + i { + color: var(--dark-grey); + } + } + + app-button { + position: absolute; + top: 82%; + left: 41%; + } + + &__info { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 2px; + margin-top: 8px; + + p { + width: 60%; + text-align: center; + color: var(--accent); + } + } +} + .about { padding: 24px; background-color: var(--light-white); @@ -284,21 +373,13 @@ flex-direction: column; align-items: center; justify-content: center; - width: 80%; max-height: calc(100vh - 40px); - padding: 40px 0 80px; - overflow-y: auto; - - @include responsive.apply-desktop { - width: 50%; - } + padding: 24px 54px; &__cross { position: absolute; top: 0; right: 0; - width: 32px; - height: 32px; cursor: pointer; @include responsive.apply-desktop { @@ -311,6 +392,8 @@ display: flex; flex-direction: column; margin-bottom: 10px; + gap: 10px; + align-items: center; } &__title { diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.ts b/projects/social_platform/src/app/office/program/detail/main/main.component.ts index 913ed1f3e..d37c8b9ec 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.ts +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.ts @@ -28,7 +28,7 @@ import { expandElement } from "@utils/expand-element"; import { ParseBreaksPipe, ParseLinksPipe } from "projects/core"; import { UserLinksPipe } from "@core/pipes/user-links.pipe"; import { ProgramNewsCardComponent } from "../shared/news-card/news-card.component"; -import { ButtonComponent, IconComponent } from "@ui/components"; +import { ButtonComponent, IconComponent, InputComponent } from "@ui/components"; import { AvatarComponent } from "@ui/components/avatar/avatar.component"; import { ApiPagination } from "@models/api-pagination.model"; import { TagComponent } from "@ui/components/tag/tag.component"; @@ -38,6 +38,7 @@ import { ModalComponent } from "@ui/components/modal/modal.component"; import { MatProgressBarModule } from "@angular/material/progress-bar"; import { AsyncPipe } from "@angular/common"; import { LoadingService } from "@office/services/loading.service"; +import { Project } from "@office/models/project.model"; @Component({ selector: "app-main", @@ -58,6 +59,7 @@ import { LoadingService } from "@office/services/loading.service"; NewsFormComponent, ModalComponent, MatProgressBarModule, + InputComponent, ], }) export class ProgramDetailMainComponent implements OnInit, OnDestroy { @@ -79,6 +81,10 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { showProgramModal = signal(false); showProgramModalErrorMessage = signal(null); + showSubmitProjectModal = signal(false); + + selectedProjectId = 0; + programId?: number; subscriptions$ = signal([]); @@ -142,7 +148,14 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { }, }); + const memeberProjects$ = this.projectService.getMy().subscribe({ + next: projects => { + this.memberProjects = projects.results; + }, + }); + this.subscriptions$().push(program$); + this.subscriptions$().push(memeberProjects$); this.subscriptions$().push(programIdSubscription$); this.subscriptions$().push(routeModalSub$); } @@ -161,8 +174,6 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { .subscribe(); this.subscriptions$().push(scrollEvents$); } - - console.log(this.showDetails, this.program?.isUserMember); } ngOnDestroy(): void { @@ -254,6 +265,56 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { this.loadingService.hide(); } + /** + * Переключатель для модалки выбора проекта + */ + toggleSubmitProjectModal(): void { + this.showSubmitProjectModal.set(!this.showSubmitProjectModal()); + + if (!this.showSubmitProjectModal()) { + this.selectedProjectId = 0; + } + } + + /** + * Добавление проекта на программу + */ + addProjectModal(): void { + if (!this.selectedProjectId) { + return; + } + + const selectedProject = this.memberProjects.find( + project => project.id === this.selectedProjectId + ); + + console.log("Submitting project:", { + projectId: this.selectedProjectId, + projectName: selectedProject?.name, + programId: this.programId, + }); + + this.toggleSubmitProjectModal(); + this.selectedProjectId = 0; + } + + /** + * Обработчик изменения радио-кнопки для выбора проекта + */ + onProjectRadioChange(event: Event): void { + const target = event.target as HTMLInputElement; + this.selectedProjectId = +target.value; + + if (this.selectedProjectId) { + const selectedProject = this.memberProjects.find( + project => project.id === this.selectedProjectId + ); + + console.log("Selected Project ID:", this.selectedProjectId); + console.log("Selected Project Info:", selectedProject); + } + } + addProject(): void { this.loadingService.show(); @@ -277,12 +338,13 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { } onOpenDetailProgram(): void { - if (!this.program?.isUserMember) return; + if (!this.program?.isUserMember && !this.program?.isUserManager) return; this.showDetails = true; } program?: Program; + memberProjects: Project[] = []; registerDateExpired!: boolean; descriptionExpandable!: boolean; readFullDescription = false; diff --git a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.html b/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.html index 7089ccc7a..154f19f0e 100644 --- a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.html +++ b/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.html @@ -1,7 +1,7 @@
- +
- + @if (registerForm && schema) { @for (f of schema | keyvalue; track f.key) { diff --git a/projects/social_platform/src/app/office/program/detail/shared/news-card/news-card.component.html b/projects/social_platform/src/app/office/program/detail/shared/news-card/news-card.component.html index d7b88b035..57a31894c 100644 --- a/projects/social_platform/src/app/office/program/detail/shared/news-card/news-card.component.html +++ b/projects/social_platform/src/app/office/program/detail/shared/news-card/news-card.component.html @@ -5,31 +5,28 @@
-
{{ newsItem.name }}
+
{{ newsItem.name }}
@if (newsItem.pin) { - + }
-
- {{ newsItem.datetimeCreated | dayjs: "format":"DD MMMM YYYY, HH:mm" }} -
@if(isOwner) {
- +
@if (menuOpen) {
    -
  • Удалить
  • +
  • Удалить
}
}
@if (newsItem.text) { -
+

} @if (editMode) { @@ -90,21 +87,25 @@

+ + +
+
+

ошибка привязки проекта к программе!

+
+ +

+ {{ (errorAssignProjectToProgramModalMessage()?.non_field_errors)![0] }} +

+ + понятно +
+
+ + +
+
+

поздравляем!

+
+ +

+ мы создали дубликат проекта, который вы привязали к выбранной программе + {{ assignProjectToProgramModalMessage()?.partnerProgram }}, теперь его можно отредактировать! +

+ + вперед +
+
} diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.scss b/projects/social_platform/src/app/office/program/detail/main/main.component.scss index 164bcb6dc..e34266218 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.scss +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.scss @@ -86,7 +86,6 @@ } .project { - margin-top: 40px; &__list { display: flex; @@ -107,6 +106,7 @@ align-items: center; justify-content: space-between; width: 300px; + margin-left: 40px; margin-bottom: 12px; &--info { @@ -375,18 +375,6 @@ max-height: calc(100vh - 40px); padding: 24px 54px; - &__cross { - position: absolute; - top: 0; - right: 0; - cursor: pointer; - - @include responsive.apply-desktop { - top: 8px; - right: 8px; - } - } - &__top { display: flex; flex-direction: column; @@ -401,6 +389,8 @@ &__text { text-align: center; + color: var(--dark-grey); + width: 40%; } &__button { diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.ts b/projects/social_platform/src/app/office/program/detail/main/main.component.ts index d37c8b9ec..ed84418af 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.ts +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.ts @@ -39,6 +39,9 @@ import { MatProgressBarModule } from "@angular/material/progress-bar"; import { AsyncPipe } from "@angular/common"; import { LoadingService } from "@office/services/loading.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"; @Component({ selector: "app-main", @@ -67,23 +70,46 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { private readonly programService: ProgramService, private readonly programNewsService: ProgramNewsService, private readonly projectService: ProjectService, + private readonly projectAdditionalService: ProjectAdditionalService, private readonly router: Router, private readonly route: ActivatedRoute, private readonly cdRef: ChangeDetectorRef, private readonly loadingService: LoadingService ) {} + get isAssignProjectToProgramError() { + return this.projectAdditionalService.getIsAssignProjectToProgramError()(); + } + + get errorAssignProjectToProgramModalMessage() { + return this.projectAdditionalService.getErrorAssignProjectToProgramModalMessage(); + } + news = signal([]); totalNewsCount = signal(0); fetchLimit = signal(10); fetchPage = signal(0); + // Сигналы для работы с модальными окнами с текстом showProgramModal = signal(false); showProgramModalErrorMessage = signal(null); + assignProjectToProgramModalMessage = signal(null); + // Сигналы для управления состоянием showSubmitProjectModal = signal(false); + isAssignProjectToProgramModalOpen = signal(false); + + // Методы для управления состоянием ошибок через сервис + setAssignProjectToProgramError(error: { non_field_errors: string[] }): void { + this.projectAdditionalService.setAssignProjectToProgramError(error); + } + + clearAssignProjectToProgramError(): void { + this.projectAdditionalService.clearAssignProjectToProgramError(); + } selectedProjectId = 0; + dubplicatedProjectId = 0; programId?: number; @@ -150,7 +176,7 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { const memeberProjects$ = this.projectService.getMy().subscribe({ next: projects => { - this.memberProjects = projects.results; + this.memberProjects = projects.results.filter(project => !project.draft); }, }); @@ -276,6 +302,18 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { } } + /** + * Обработчик изменения радио-кнопки для выбора проекта + */ + 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); + } + } + /** * Добавление проекта на программу */ @@ -288,53 +326,41 @@ export class ProgramDetailMainComponent implements OnInit, OnDestroy { project => project.id === this.selectedProjectId ); - console.log("Submitting project:", { - projectId: this.selectedProjectId, - projectName: selectedProject?.name, - programId: this.programId, - }); - - this.toggleSubmitProjectModal(); - this.selectedProjectId = 0; + this.assignProjectToProgram(selectedProject!); } + /** Эмитим логику для привязки проекта к программе */ /** - * Обработчик изменения радио-кнопки для выбора проекта + * Привязка проекта к программе выбранной + * Перенаправление её на редактирование "нового" проекта */ - onProjectRadioChange(event: Event): void { - const target = event.target as HTMLInputElement; - this.selectedProjectId = +target.value; - - if (this.selectedProjectId) { - const selectedProject = this.memberProjects.find( - project => project.id === this.selectedProjectId - ); + assignProjectToProgram(project: Project): void { + if (this.programId) { + this.projectService.assignProjectToProgram(project.id, this.programId).subscribe({ + next: r => { + this.dubplicatedProjectId = r.newProjectId; + this.assignProjectToProgramModalMessage.set(r); + this.isAssignProjectToProgramModalOpen.set(true); + this.toggleSubmitProjectModal(); + this.selectedProjectId = 0; + }, - console.log("Selected Project ID:", this.selectedProjectId); - console.log("Selected Project Info:", selectedProject); + error: err => { + if (err instanceof HttpErrorResponse) { + if (err.status === 400) { + this.setAssignProjectToProgramError(err.error); + } + } + }, + }); } } - addProject(): void { - this.loadingService.show(); - - this.projectService.create().subscribe({ - next: project => { - this.projectService.projectsCount.next({ - ...this.projectService.projectsCount.getValue(), - my: this.projectService.projectsCount.getValue().my + 1, - }); - this.router - .navigateByUrl(`/office/projects/${project.id}/edit?editingStep=main`) - .then(() => { - console.debug("Route change from ProjectsComponent"); - }); - }, - error: error => { - this.loadingService.hide(); - console.error("Project creation error:", error); - }, - }); + closeAssignProjectToProgramModal(): void { + this.isAssignProjectToProgramModalOpen.set(false); + this.router.navigateByUrl( + `/office/projects/${this.dubplicatedProjectId}/edit?editingStep=main` + ); } onOpenDetailProgram(): void { 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 397fcd89b..f1f6eb776 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 @@ -109,13 +109,4 @@ export class ProgramService { {} ); } - - programTags$ = new BehaviorSubject([]); - programTags(): Observable { - return this.apiService.get(`${this.AUTH_USERS_CURRENT_URL}/programs/tags/`).pipe( - tap(programs => { - this.programTags$.next(programs); - }) - ); - } } 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 c0c094c1d..c69451dfe 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 @@ -12,13 +12,18 @@

- Регистрация до: - {{ program.datetimeRegistrationEnds | date: "dd MMMM" }} + {{ + !registerDateExpired && (!program.isUserMember || !program.isUserManager) + ? "Регистрация до:" + " " + (program.datetimeRegistrationEnds | date: "dd MMMM") + : registerDateExpired + ? "программа завершена" + : "ты уже участвуешь!" + }}

diff --git a/projects/social_platform/src/app/office/program/shared/program-card/program-card.component.ts b/projects/social_platform/src/app/office/program/shared/program-card/program-card.component.ts index 2cb0f28fd..41aa9fd8a 100644 --- a/projects/social_platform/src/app/office/program/shared/program-card/program-card.component.ts +++ b/projects/social_platform/src/app/office/program/shared/program-card/program-card.component.ts @@ -43,8 +43,9 @@ export class ProgramCardComponent implements OnInit { @Input({ required: true }) program?: Program; - ngOnInit(): void {} + ngOnInit(): void { + this.registerDateExpired = Date.now() > Date.parse(this.program!.datetimeRegistrationEnds); + } - // TODO: изменить когда у нас появятся в информации о программе состояние о завершенности/участии - type = "actual"; + registerDateExpired?: boolean; } diff --git a/projects/social_platform/src/app/office/projects/edit/edit.component.html b/projects/social_platform/src/app/office/projects/edit/edit.component.html index 18f21c127..da51ca9b9 100644 --- a/projects/social_platform/src/app/office/projects/edit/edit.component.html +++ b/projects/social_platform/src/app/office/projects/edit/edit.component.html @@ -45,11 +45,9 @@

редактировать проект

} @else if (editingStep === "contacts") { @@ -74,7 +72,6 @@

редактировать проект

}
@@ -98,67 +95,6 @@

📢 Внимание!

- -
-
- -

Ошибка привязки проекта к программе!

-
- -

- {{ (errorAssignProjectToProgramModalMessage()?.non_field_errors)![0] }} -

- - Понятно -
-
- - -
-
- -

Поздравляем!

-
- -

- Мы создали дубликат проекта, который вы привязали к выбранной программе - {{ assignProjectToProgramModalMessage()?.partnerProgram }}, теперь его можно отредактировать! -

- - Понятно -
-
-
diff --git a/projects/social_platform/src/app/office/projects/edit/edit.component.scss b/projects/social_platform/src/app/office/projects/edit/edit.component.scss index 56625fa6b..081e9b06d 100644 --- a/projects/social_platform/src/app/office/projects/edit/edit.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/edit.component.scss @@ -12,13 +12,13 @@ &__top { position: fixed; top: 5%; - left: 12%; + left: 6%; z-index: 10; display: flex; gap: 12%; align-items: center; justify-content: space-evenly; - width: 88%; + width: 100%; background-color: var(--white); border-radius: var(--rounded-xxl); } 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 e8b5d0c1f..46a48e77a 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 @@ -106,7 +106,7 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { private readonly snackBarService: SnackbarService, private readonly skillsService: SkillsService, private readonly projectAdditionalService: ProjectAdditionalService, - private readonly projectGoalService: ProjectGoalService // Добавляем ProjectGoalService + private readonly projectGoalService: ProjectGoalService ) {} // Получаем форму проекта из сервиса @@ -144,23 +144,6 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { return this.projectAdditionalService.getPartnerProgramFields(); } - get isAssignProjectToProgramError() { - return this.projectAdditionalService.getIsAssignProjectToProgramError()(); - } - - get errorAssignProjectToProgramModalMessage() { - return this.projectAdditionalService.getErrorAssignProjectToProgramModalMessage(); - } - - // Методы для управления состоянием ошибок через сервис - setAssignProjectToProgramError(error: { non_field_errors: string[] }): void { - this.projectAdditionalService.setAssignProjectToProgramError(error); - } - - clearAssignProjectToProgramError(): void { - this.projectAdditionalService.clearAssignProjectToProgramError(); - } - ngOnInit(): void { this.navService.setNavTitle("Создание проекта"); @@ -186,7 +169,6 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { // Опции для программных тегов programTagsOptions: SelectComponent["options"] = []; - programTags: ProgramTag[] = []; // Id Лидера проекта leaderId = 0; @@ -194,9 +176,6 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { // Маркер того является ли проект привязанный к конкурсной программе isCompetitive = false; - // Маркер что проект привязан - isProjectBoundToProgram = false; - // Текущий шаг редактирования get editingStep(): EditStep { return this.projectStepService.getCurrentStep()(); @@ -223,9 +202,6 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { onEditClicked = signal(false); warningModalSeen = false; - // Сигналы для работы с модальными окнами с текстом - assignProjectToProgramModalMessage = signal(null); - // Observables для данных industries$ = this.industryService.industries.pipe( map(industries => @@ -235,13 +211,12 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { subscriptions: (Subscription | undefined)[] = []; - profileId: number = this.route.snapshot.params["projectId"]; + profileId: number = +this.route.snapshot.params["projectId"]; // Сигналы для управления состоянием inlineSkills = signal([]); nestedSkills$ = this.skillsService.getSkillsNested(); skillsGroupsModalOpen = signal(false); - isAssignProjectToProgramModalOpen = signal(false); // Состояние отправки форм projSubmitInitiated = false; @@ -265,32 +240,6 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { } } - /** - * Привязка проекта к программе выбранной - * Перенаправление её на редактирование "нового" проекта - */ - 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); - }, - - error: err => { - if (err instanceof HttpErrorResponse) { - if (err.status === 400) { - this.setAssignProjectToProgramError(err.error); - } - } - }, - }); - } - // Методы для управления состоянием отправки форм setIsSubmittingAsPublished(status: boolean): void { this.projFormIsSubmittingAsPublished = status; @@ -430,11 +379,6 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { this.sendAdditionalFields(projectId, relationId); } - closeAssignProjectToProgramModal(): void { - this.isAssignProjectToProgramModalOpen.set(false); - this.router.navigateByUrl(`/office/projects/my`); - } - /** * Валидация дополнительных полей для публикации * Делегирует валидацию сервису @@ -563,22 +507,8 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { } private loadProgramTagsAndProject(): void { - this.programService - .programTags() - .pipe( - tap(tags => { - this.programTags = tags; - }), - map(tags => [ - { label: "Без тега", value: 0, id: 0 }, - ...tags.map(t => ({ label: t.name, value: t.id, id: t.id })), - ]), - tap(tags => { - this.programTagsOptions = tags; - }), - concatMap(() => this.route.data), - map(d => d["data"]) - ) + this.route.data + .pipe(map(d => d["data"])) .subscribe(([project, goals, invites]: [Project, Goal[], Invite[]]) => { // Используем сервис для инициализации данных проекта this.projectFormService.initializeProjectData(project); @@ -588,7 +518,6 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { if (project.partnerProgram) { this.isCompetitive = project.partnerProgram.canSubmit; - this.isProjectBoundToProgram = !!project.partnerProgram.programId; this.projectAdditionalService.initializeAdditionalForm( project.partnerProgram?.programFields, diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.ts b/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.ts index 7eb2981e2..aba9d8d48 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.ts +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.ts @@ -42,8 +42,6 @@ import { TooltipComponent } from "@ui/components/tooltip/tooltip.component"; ], }) export class ProjectAdditionalStepComponent implements OnInit { - @Input() programTagsOptions: any[] = []; - private readonly projectAdditionalService = inject(ProjectAdditionalService); private readonly cdRef = inject(ChangeDetectorRef); 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 c1f8f8376..5f4c992c0 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 @@ -263,24 +263,8 @@ class="project__options" [formGroup]="goalForm" [style.gap]="hasGoals || hasLinks ? '0px' : '35px'" + [style.margin-top]="hasGoals && hasLinks ? '20px' : hasGoals ? '20px' : '35px'" > - - @if (hasGoals) { - @if (isAll) { -
-
-
-
- -
-
- } @else if (!projects.length && isMy) { -
- - или - - - Найдите интересующий проект в списке - -
- } diff --git a/projects/social_platform/src/app/office/projects/list/list.component.scss b/projects/social_platform/src/app/office/projects/list/list.component.scss index 92edf345e..361776747 100644 --- a/projects/social_platform/src/app/office/projects/list/list.component.scss +++ b/projects/social_platform/src/app/office/projects/list/list.component.scss @@ -30,8 +30,8 @@ display: grid; flex-grow: 1; grid-template-columns: 1fr; - row-gap: 20px; - column-gap: 40px; + row-gap: 40px; + column-gap: 20px; align-items: flex-start; @include responsive.apply-desktop { diff --git a/projects/social_platform/src/app/office/projects/list/list.component.ts b/projects/social_platform/src/app/office/projects/list/list.component.ts index 9e194af48..f54b50077 100644 --- a/projects/social_platform/src/app/office/projects/list/list.component.ts +++ b/projects/social_platform/src/app/office/projects/list/list.component.ts @@ -5,6 +5,7 @@ import { ChangeDetectorRef, Component, ElementRef, + inject, OnDestroy, OnInit, Renderer2, @@ -28,13 +29,14 @@ import { Project } from "@models/project.model"; import { User } from "@auth/models/user.model"; import { NavService } from "@services/nav.service"; import { ProjectService } from "@services/project.service"; -import Fuse from "fuse.js"; import { HttpParams } from "@angular/common/http"; import { ApiPagination } from "@models/api-pagination.model"; import { ProjectsFilterComponent } from "../projects-filter/projects-filter.component"; import { ProjectCardComponent } from "../../shared/project-card/project-card.component"; import { IconComponent } from "@ui/components"; import { SubscriptionService } from "@office/services/subscription.service"; +import { Invite } from "@office/models/invite.model"; +import { InviteService } from "@office/services/invite.service"; /** * КОМПОНЕНТ СПИСКА ПРОЕКТОВ @@ -69,7 +71,7 @@ import { SubscriptionService } from "@office/services/subscription.service"; * - searchedProjects[] - отфильтрованный список для отображения * - profile - данные текущего пользователя * - isFilterOpen - состояние панели фильтров (мобильные) - * - isAll/isMy/isSubs - флаги текущего режима просмотра + * - isAll/isMy/isSubs/isInvites - флаги текущего режима просмотра * * Жизненный цикл: * - OnInit: настройка подписок, инициализация данных @@ -87,19 +89,17 @@ import { SubscriptionService } from "@office/services/subscription.service"; templateUrl: "./list.component.html", styleUrl: "./list.component.scss", standalone: true, - imports: [IconComponent, RouterLink, ProjectCardComponent, ProjectsFilterComponent], + imports: [IconComponent, RouterLink, ProjectCardComponent], }) export class ProjectsListComponent implements OnInit, AfterViewInit, OnDestroy { - constructor( - private readonly route: ActivatedRoute, - private readonly authService: AuthService, - private readonly navService: NavService, - private readonly projectService: ProjectService, - private readonly cdref: ChangeDetectorRef, - private readonly router: Router, - private readonly subscriptionService: SubscriptionService, - private readonly renderer: Renderer2 - ) {} + private readonly renderer = inject(Renderer2); + private readonly route = inject(ActivatedRoute); + private readonly authService = inject(AuthService); + private readonly navService = inject(NavService); + private readonly projectService = inject(ProjectService); + private readonly cdref = inject(ChangeDetectorRef); + private readonly router = inject(Router); + private readonly subscriptionService = inject(SubscriptionService); @ViewChild("filterBody") filterBody!: ElementRef; @@ -111,6 +111,7 @@ export class ProjectsListComponent implements OnInit, AfterViewInit, OnDestroy { this.isMy = location.href.includes("/my"); this.isAll = location.href.includes("/all"); this.isSubs = location.href.includes("/subsription"); + this.isInvites = location.href.includes("/invites"); } }); routeUrl$ && this.subscriptions$.push(routeUrl$); @@ -178,7 +179,20 @@ export class ProjectsListComponent implements OnInit, AfterViewInit, OnDestroy { const projects$ = this.route.data.pipe(map(r => r["data"])).subscribe(projects => { this.projectsCount = projects.count; - this.projects = projects.results ?? []; + if (this.isInvites) { + this.projects = (projects ?? []).map((invite: Invite) => ({ + inviteId: invite.id, + id: invite.project.id, + imageAddress: invite.project.imageAddress, + vacancies: invite.project.vacancies, + collaborators: invite.project.collaborators, + name: invite.project.name, + shortDescription: invite.user.firstName + " " + invite.user.lastName, + industry: invite.project.industry, + })); + } else { + this.projects = projects.results ?? []; + } }); projects$ && this.subscriptions$.push(projects$); @@ -235,6 +249,7 @@ export class ProjectsListComponent implements OnInit, AfterViewInit, OnDestroy { isAll = location.href.includes("/all"); isMy = location.href.includes("/my"); isSubs = location.href.includes("/subscriptions"); + isInvites = location.href.includes("/invites"); profile?: User; profileProjSubsIds?: number[]; @@ -251,33 +266,36 @@ export class ProjectsListComponent implements OnInit, AfterViewInit, OnDestroy { private previousReqQuery: Record = {}; - deleteProject(projectId: number): void { - if (!confirm("Вы точно хотите удалить проект?")) { - return; - } + // deleteProject(projectId: number): void { + // if (!confirm("Вы точно хотите удалить проект?")) { + // return; + // } - this.projectService.remove(projectId).subscribe(() => { - this.projectService.projectsCount.next({ - ...this.projectService.projectsCount.getValue(), - my: this.projectService.projectsCount.getValue().my - 1, - }); + // this.projectService.remove(projectId).subscribe(() => { + // this.projectService.projectsCount.next({ + // ...this.projectService.projectsCount.getValue(), + // my: this.projectService.projectsCount.getValue().my - 1, + // }); - const index = this.projects.findIndex(project => project.id === projectId); - this.projects.splice(index, 1); - }); + // const index = this.projects.findIndex(project => project.id === projectId); + // this.projects.splice(index, 1); + // }); + // } + + onAcceptInvite(event: number): void { + this.sliceInvitesArray(event); } - addProject(): void { - this.projectService.create().subscribe(project => { - this.projectService.projectsCount.next({ - ...this.projectService.projectsCount.getValue(), - my: this.projectService.projectsCount.getValue().my + 1, - }); + onRejectInvite(event: number): void { + this.sliceInvitesArray(event); + } - this.router - .navigateByUrl(`/office/projects/${project.id}/edit`) - .then(() => console.debug("Route change from ProjectsComponent")); - }); + private sliceInvitesArray(inviteId: number): void { + const index = this.projects.findIndex(p => p.inviteId === inviteId); + if (index !== -1) { + this.projects.splice(index, 1); + this.projectsCount = Math.max(0, this.projectsCount - 1); + } } private swipeStartY = 0; @@ -322,8 +340,8 @@ export class ProjectsListComponent implements OnInit, AfterViewInit, OnDestroy { this.isFilterOpen = false; } - onScroll() { - if (this.isSubs) { + private onScroll() { + if (this.isSubs || this.isInvites) { return of({}); } @@ -351,7 +369,7 @@ export class ProjectsListComponent implements OnInit, AfterViewInit, OnDestroy { return of({}); } - onFetch(skip: number, take: number) { + private onFetch(skip: number, take: number) { if (this.isAll) { const queries = this.route.snapshot.queryParams; diff --git a/projects/social_platform/src/app/office/projects/projects-filter/projects-filter.component.html b/projects/social_platform/src/app/office/projects/projects-filter/projects-filter.component.html index ee742638e..1ae5d46d0 100644 --- a/projects/social_platform/src/app/office/projects/projects-filter/projects-filter.component.html +++ b/projects/social_platform/src/app/office/projects/projects-filter/projects-filter.component.html @@ -2,10 +2,7 @@
-
-

Фильтр

- Сбросить фильтры -
+
-
+ + +
-

Направление

- @for (industry of industries; track industry.id) { -
- - {{ industry.name }} +
+
- }
diff --git a/projects/social_platform/src/app/office/projects/projects-filter/projects-filter.component.scss b/projects/social_platform/src/app/office/projects/projects-filter/projects-filter.component.scss index 5df138ba7..9384ceccb 100644 --- a/projects/social_platform/src/app/office/projects/projects-filter/projects-filter.component.scss +++ b/projects/social_platform/src/app/office/projects/projects-filter/projects-filter.component.scss @@ -31,7 +31,6 @@ &__body { max-width: 280px; - padding: 22px; background-color: var(--white); border-radius: var(--rounded-md); } @@ -47,16 +46,6 @@ cursor: pointer; } - &__title { - color: var(--black); - - @include typography.heading-3; - - @include responsive.apply-desktop { - @include typography.bold-body-16; - } - } - &__block { margin-bottom: 14px; } diff --git a/projects/social_platform/src/app/office/projects/projects-filter/projects-filter.component.ts b/projects/social_platform/src/app/office/projects/projects-filter/projects-filter.component.ts index b25e273cd..41c0e4cac 100644 --- a/projects/social_platform/src/app/office/projects/projects-filter/projects-filter.component.ts +++ b/projects/social_platform/src/app/office/projects/projects-filter/projects-filter.component.ts @@ -2,15 +2,17 @@ import { Component, OnInit } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; -import { Subscription } from "rxjs"; +import { map, Subscription } from "rxjs"; import { ProjectStep } from "@models/project.model"; import { Industry } from "@models/industry.model"; import { IndustryService } from "@services/industry.service"; import { ProjectService } from "@services/project.service"; import { SwitchComponent } from "@ui/components/switch/switch.component"; import { NumSliderComponent } from "@ui/components/num-slider/num-slider.component"; -import { CheckboxComponent } from "@ui/components"; +import { CheckboxComponent, SelectComponent } from "@ui/components"; import { filterTags } from "projects/core/src/consts/filter-tags"; +import { generateOptionsList, optionsListElement } from "@utils/generate-options-list"; +import { FormControl, ReactiveFormsModule } from "@angular/forms"; /** * Компонент фильтрации проектов @@ -43,7 +45,13 @@ import { filterTags } from "projects/core/src/consts/filter-tags"; templateUrl: "./projects-filter.component.html", styleUrl: "./projects-filter.component.scss", standalone: true, - imports: [CheckboxComponent, NumSliderComponent, SwitchComponent], + imports: [ + CheckboxComponent, + NumSliderComponent, + SwitchComponent, + SelectComponent, + ReactiveFormsModule, + ], }) export class ProjectsFilterComponent implements OnInit { constructor( @@ -58,8 +66,23 @@ export class ProjectsFilterComponent implements OnInit { ngOnInit(): void { // Подписка на данные об отраслях - this.industries$ = this.industryService.industries.subscribe(industries => { - this.industries = industries; + this.industries$ = this.industryService.industries + .pipe( + map(industries => + industries.map(industry => ({ + id: industry.id, + label: industry.name, + value: industry.name, + })) + ) + ) + .subscribe(industries => { + this.industries = industries; + }); + + this.industryControl.valueChanges.subscribe(value => { + const industryId = this.industries.find(industry => industry.value === value); + this.onFilterByIndustry(industryId?.id); }); // Восстановление состояния фильтров из query параметров @@ -81,9 +104,11 @@ export class ProjectsFilterComponent implements OnInit { // Подписки для управления жизненным циклом queries$?: Subscription; + industryControl = new FormControl(null); + // Состояние фильтра по отрасли currentIndustry: number | null = null; - industries: Industry[] = []; + industries: optionsListElement[] = []; industries$?: Subscription; // Состояние остальных фильтров @@ -102,9 +127,7 @@ export class ProjectsFilterComponent implements OnInit { * @param event - событие клика * @param industryId - ID отрасли (undefined для сброса) */ - onFilterByIndustry(event: Event, industryId?: number): void { - event.stopPropagation(); - + onFilterByIndustry(industryId?: number | null): void { this.router .navigate([], { queryParams: { industry: industryId === this.currentIndustry ? undefined : industryId }, diff --git a/projects/social_platform/src/app/office/projects/projects.component.html b/projects/social_platform/src/app/office/projects/projects.component.html index 6f0fc0f58..662514329 100644 --- a/projects/social_platform/src/app/office/projects/projects.component.html +++ b/projects/social_platform/src/app/office/projects/projects.component.html @@ -46,7 +46,7 @@
-
+
-
-
-
-
- - пока закрыто -
- скоро -
- -
-

статистика

-

количество проектов, команды, эффективность

-
+ @if (isMy) { + + + } @if (isAll) { +
+
+
+
+
+ }
diff --git a/projects/social_platform/src/app/office/projects/projects.component.scss b/projects/social_platform/src/app/office/projects/projects.component.scss index ec2fe83bf..60e5de94e 100644 --- a/projects/social_platform/src/app/office/projects/projects.component.scss +++ b/projects/social_platform/src/app/office/projects/projects.component.scss @@ -25,59 +25,4 @@ gap: 20px; align-items: center; } - - &__analytics { - padding: 12px; - background-color: var(--light-white); - border: 0.5px solid var(--medium-grey-for-outline); - border-radius: var(--rounded-lg); - } -} - -.analytics { - text-align: center; - - &__soon { - position: relative; - margin-bottom: 4px; - border: 0.5px solid var(--medium-grey-for-outline); - border-radius: var(--rounded-lg); - } - - &__closed { - display: flex; - flex-direction: column; - gap: 2px; - align-items: center; - justify-content: center; - padding: 24px 37px; - - &--text { - color: var(--dark-grey); - } - - i { - color: var(--dark-grey); - } - } - - app-button { - position: absolute; - top: 82%; - left: 36%; - } - - &__info { - display: flex; - flex-direction: column; - gap: 2px; - align-items: center; - justify-content: center; - margin-top: 8px; - - p { - color: var(--accent); - text-align: center; - } - } } diff --git a/projects/social_platform/src/app/office/projects/projects.component.ts b/projects/social_platform/src/app/office/projects/projects.component.ts index 7719b3b65..0ca12b2d3 100644 --- a/projects/social_platform/src/app/office/projects/projects.component.ts +++ b/projects/social_platform/src/app/office/projects/projects.component.ts @@ -1,6 +1,6 @@ /** @format */ -import { Component, OnDestroy, OnInit } from "@angular/core"; +import { Component, ElementRef, OnDestroy, OnInit, Renderer2, ViewChild } from "@angular/core"; import { NavService } from "@services/nav.service"; import { ActivatedRoute, NavigationEnd, Router, RouterOutlet } from "@angular/router"; import { map, Subscription } from "rxjs"; @@ -12,6 +12,8 @@ import { ButtonComponent, IconComponent } from "@ui/components"; import { AsyncPipe } from "@angular/common"; import { BarNewComponent } from "@ui/components/bar-new/bar.component"; import { BackComponent } from "@uilib"; +import { SoonCardComponent } from "@office/shared/soon-card/soon-card.component"; +import { ProjectsFilterComponent } from "./projects-filter/projects-filter.component"; /** * Главный компонент модуля проектов @@ -37,8 +39,9 @@ import { BackComponent } from "@uilib"; ButtonComponent, RouterOutlet, BarNewComponent, - AsyncPipe, BackComponent, + SoonCardComponent, + ProjectsFilterComponent, ], }) export class ProjectsComponent implements OnInit, OnDestroy { @@ -47,6 +50,7 @@ export class ProjectsComponent implements OnInit, OnDestroy { private readonly route: ActivatedRoute, public readonly projectService: ProjectService, private readonly router: Router, + private readonly renderer: Renderer2, private readonly fb: FormBuilder ) { this.searchForm = this.fb.group({ @@ -54,6 +58,8 @@ export class ProjectsComponent implements OnInit, OnDestroy { }); } + @ViewChild("filterBody") filterBody!: ElementRef; + ngOnInit(): void { this.navService.setNavTitle("Проекты"); @@ -97,6 +103,51 @@ export class ProjectsComponent implements OnInit, OnDestroy { isMy = location.href.includes("/my"); isAll = location.href.includes("/all"); isSubs = location.href.includes("/subscriptions"); + isInvites = location.href.includes("/invites"); + + isFilterOpen = false; + + private swipeStartY = 0; + private swipeThreshold = 50; + private isSwiping = false; + + onSwipeStart(event: TouchEvent): void { + this.swipeStartY = event.touches[0].clientY; + this.isSwiping = true; + } + + onSwipeMove(event: TouchEvent): void { + if (!this.isSwiping) return; + + const currentY = event.touches[0].clientY; + const deltaY = currentY - this.swipeStartY; + + const progress = Math.min(deltaY / this.swipeThreshold, 1); + this.renderer.setStyle( + this.filterBody.nativeElement, + "transform", + `translateY(${progress * 100}px)` + ); + } + + onSwipeEnd(event: TouchEvent): void { + if (!this.isSwiping) return; + + const endY = event.changedTouches[0].clientY; + const deltaY = endY - this.swipeStartY; + + if (deltaY > this.swipeThreshold) { + this.closeFilter(); + } + + this.isSwiping = false; + + this.renderer.setStyle(this.filterBody.nativeElement, "transform", "translateY(0)"); + } + + closeFilter(): void { + this.isFilterOpen = false; + } addProject(): void { this.projectService.create().subscribe(project => { diff --git a/projects/social_platform/src/app/office/projects/projects.routes.ts b/projects/social_platform/src/app/office/projects/projects.routes.ts index 316bd3840..7e555d7fd 100644 --- a/projects/social_platform/src/app/office/projects/projects.routes.ts +++ b/projects/social_platform/src/app/office/projects/projects.routes.ts @@ -10,6 +10,7 @@ import { ProjectEditComponent } from "./edit/edit.component"; import { ProjectEditResolver } from "./edit/edit.resolver"; import { ProjectsSubscriptionsResolver } from "./list/subscriptions.resolver"; import { ProjectEditRequiredGuard } from "./edit/guards/projects-edit.guard"; +import { ProjectsInvitesResolver } from "./list/invites.resolver"; /** * Конфигурация маршрутов для модуля проектов @@ -61,6 +62,13 @@ export const PROJECTS_ROUTES: Routes = [ data: ProjectsSubscriptionsResolver, }, }, + { + path: "invites", + component: ProjectsListComponent, + resolve: { + data: ProjectsInvitesResolver, + }, + }, { path: "all", component: ProjectsListComponent, diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.html b/projects/social_platform/src/app/office/shared/project-card/project-card.component.html index 341a09032..806865f67 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.html +++ b/projects/social_platform/src/app/office/shared/project-card/project-card.component.html @@ -2,6 +2,61 @@
+ @if (type === 'project') { +
+ +
+

12

+ +
+ +
+

12

+ +
+
+ } @if (haveBadge) { @if (isSubscribed) { + + } @else { + + } } + + +
+

Вы действительно хотите отписаться от проекта?

+ +
+ + Отписаться + + + Отменить + +
+
+
+
@@ -18,11 +73,59 @@ }
+ @if (type === 'invite') { +

вас приглашает

+

{{ project.shortDescription }}

+ } @else {

{{ project.shortDescription }}

+ }
-

проект

+ @if (type === 'project') { + проект + } @else { + принять + + отклонить + }
+ + +
+

+ Приглашение на текущий проект было удалено +

+

+ Проверьте наличие вас в списке участников проекта или обратитесь к создателю проекта, + чтобы вас заново пригласили! +

+ + Хорошо + +
+
diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss b/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss index 4365547ae..d7b2533fe 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss +++ b/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss @@ -7,7 +7,7 @@ &__body { position: relative; - padding: 50px 0 12px; + padding: 15px 0 12px; background-color: var(--light-white); border: 0.5px solid var(--medium-for-outline); border-radius: var(--rounded-xl); @@ -16,7 +16,8 @@ &__photo { position: absolute; bottom: 72%; - left: 36%; + left: 50%; + transform: translateX(-50%); } &__content { @@ -25,12 +26,42 @@ align-items: center; justify-content: center; text-align: center; + padding-top: 40px; } &__head { margin-bottom: 15px; } + &__info { + display: flex; + align-items: center; + justify-content: space-evenly; + gap: 10px; + + &--vacancies, &--collaborators { + display: flex; + align-items: center; + gap: 4px; + color: var(--grey-for-text); + } + } + + &__subscribe-badge { + display: block; + transform: translateY(10px); + color: var(--accent); + cursor: pointer; + } + + &__user { + color: var(--grey-for-text); + + &--invite { + color: var(--accent); + } + } + &__name { max-width: 200px; margin-bottom: 3px; @@ -55,11 +86,36 @@ &__project { padding: 2px 24px; - background-color: var(--accent); border-radius: var(--rounded-xl); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; &--text { color: var(--white); } } } + +.message-modal { + display: flex; + flex-direction: column; + align-items: center; + max-width: 402px; + + &__title { + margin: 18px 0; + color: var(--black); + text-align: center; + } + + &__text { + color: var(--dark-grey); + text-align: center; + } + + &__button { + margin-top: 18px; + } +} diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.ts b/projects/social_platform/src/app/office/shared/project-card/project-card.component.ts index c5a2c5a2d..2ca7d9c00 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.ts +++ b/projects/social_platform/src/app/office/shared/project-card/project-card.component.ts @@ -1,11 +1,16 @@ /** @format */ -import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; +import { Component, EventEmitter, inject, Input, OnInit, Output } from "@angular/core"; import { Project } from "@models/project.model"; import { IndustryService } from "@services/industry.service"; -import { IconComponent } from "@ui/components"; +import { IconComponent, ButtonComponent } from "@ui/components"; import { AvatarComponent } from "@ui/components/avatar/avatar.component"; import { AsyncPipe, CommonModule } from "@angular/common"; +import { ModalComponent } from "@ui/components/modal/modal.component"; +import { SubscriptionService } from "@office/services/subscription.service"; +import { InviteService } from "@office/services/invite.service"; +import { Router } from "@angular/router"; +import { ClickOutsideModule } from "ng-click-outside"; /** * Компонент карточки проекта @@ -35,27 +40,100 @@ import { AsyncPipe, CommonModule } from "@angular/common"; templateUrl: "./project-card.component.html", styleUrl: "./project-card.component.scss", standalone: true, - imports: [CommonModule, AvatarComponent, IconComponent, AsyncPipe], + imports: [ + CommonModule, + AvatarComponent, + IconComponent, + AsyncPipe, + ModalComponent, + ButtonComponent, + ClickOutsideModule, + ], }) export class ProjectCardComponent implements OnInit { - constructor(public industryService: IndustryService) {} - - ngOnInit(): void {} + private readonly inviteService = inject(InviteService); + private readonly subscriptionService = inject(SubscriptionService); + private readonly router = inject(Router); + public readonly industryService = inject(IndustryService); @Input({ required: true }) project!: Project; + @Input() type: "invite" | "project" = "project"; @Input() canDelete?: boolean | null = false; @Input() isSubscribed?: boolean | null = false; @Input() profileId?: number; - @Output() remove = new EventEmitter(); + @Output() onAcceptingInvite = new EventEmitter(); + @Output() onRejectingInvite = new EventEmitter(); + + ngOnInit(): void {} + + onRejectInvite(event: Event, inviteId: number): void { + event.stopPropagation(); + event.preventDefault(); + + this.inviteService.rejectInvite(inviteId).subscribe({ + next: () => { + this.onRejectingInvite.emit(inviteId || this.project.inviteId); + }, + error: () => { + this.inviteErrorModal = true; + }, + }); + } + + onAcceptInvite(event: Event, inviteId: number): void { + event.stopPropagation(); + event.preventDefault(); + + this.inviteService.acceptInvite(inviteId).subscribe({ + next: () => { + this.onAcceptingInvite.emit(inviteId || this.project.inviteId); + }, + error: () => { + this.inviteErrorModal = true; + }, + }); + } + + isUnsubscribeModalOpen = false; // Флаг модального окна отписки + inviteErrorModal = false; // Флаг модального окна для ошибки приглашения + haveBadge = location.href.includes("/subscriptions") || location.href.includes("/all"); /** - * Обработчик удаления проекта (клик по корзине) - * Предотвращает всплытие события и эмитит событие удаления + * Подписка на проект или открытие модального окна отписки + * @param projectId - ID проекта */ - onBasket(event: MouseEvent) { + onSubscribe(event: Event, projectId: number): void { event.stopPropagation(); event.preventDefault(); - this.remove.emit(this.project.id); + + if (this.isSubscribed) { + this.isUnsubscribeModalOpen = true; + return; + } + this.subscriptionService.addSubscription(projectId).subscribe(() => { + this.isSubscribed = true; + }); + } + + /** + * Отписка от проекта + * @param projectId - ID проекта + */ + onUnsubscribe(event: Event, projectId: number): void { + event.stopPropagation(); + event.preventDefault(); + + this.subscriptionService.deleteSubscription(projectId).subscribe(() => { + this.isSubscribed = false; + this.isUnsubscribeModalOpen = false; + }); + } + + /** + * Закрытие модального окна отписки + */ + onCloseUnsubscribeModal(): void { + this.isUnsubscribeModalOpen = false; } } diff --git a/projects/social_platform/src/app/office/shared/soon-card/soon-card.component.html b/projects/social_platform/src/app/office/shared/soon-card/soon-card.component.html new file mode 100644 index 000000000..c7b98059f --- /dev/null +++ b/projects/social_platform/src/app/office/shared/soon-card/soon-card.component.html @@ -0,0 +1,20 @@ + + +
+
+
+
+ + пока закрыто +
+ скоро +
+ +
+

{{ title }}

+

{{ description }}

+
+
+
diff --git a/projects/social_platform/src/app/office/shared/soon-card/soon-card.component.scss b/projects/social_platform/src/app/office/shared/soon-card/soon-card.component.scss new file mode 100644 index 000000000..8c86c806c --- /dev/null +++ b/projects/social_platform/src/app/office/shared/soon-card/soon-card.component.scss @@ -0,0 +1,57 @@ +.analytics { + padding: 12px; + background-color: var(--light-white); + border: 0.5px solid var(--medium-grey-for-outline); + border-radius: var(--rounded-lg); + + &--wrapper { + text-align: center; + } + + &__soon { + position: relative; + margin-bottom: 4px; + border: 0.5px solid var(--medium-grey-for-outline); + border-radius: var(--rounded-lg); + display: grid; + place-items: center; + } + + &__closed { + display: flex; + flex-direction: column; + gap: 2px; + align-items: center; + justify-content: center; + padding: 24px 37px; + grid-area: 1 / 1; + + &--text { + color: var(--dark-grey); + } + + i { + color: var(--dark-grey); + } + } + + app-button { + grid-area: 1 / 1; + align-self: end; + transform: translateY(50%); + } + + &__info { + display: flex; + flex-direction: column; + gap: 2px; + align-items: center; + justify-content: center; + margin-top: 12px; + + p { + color: var(--accent); + text-align: center; + } + } +} diff --git a/projects/social_platform/src/app/office/shared/soon-card/soon-card.component.ts b/projects/social_platform/src/app/office/shared/soon-card/soon-card.component.ts new file mode 100644 index 000000000..bb04b6d69 --- /dev/null +++ b/projects/social_platform/src/app/office/shared/soon-card/soon-card.component.ts @@ -0,0 +1,19 @@ +/** @format */ + +import { CommonModule } from "@angular/common"; +import { Component, Input } from "@angular/core"; +import { ButtonComponent } from "@ui/components"; +import { IconComponent } from "@uilib"; + +@Component({ + selector: "app-soon-card", + templateUrl: "./soon-card.component.html", + styleUrl: "./soon-card.component.scss", + imports: [CommonModule, IconComponent, ButtonComponent], + standalone: true, +}) +export class SoonCardComponent { + @Input() title!: string; + + @Input() description!: string; +} diff --git a/projects/social_platform/src/app/ui/components/button/button.component.scss b/projects/social_platform/src/app/ui/components/button/button.component.scss index afd8963bd..e931c7fe0 100644 --- a/projects/social_platform/src/app/ui/components/button/button.component.scss +++ b/projects/social_platform/src/app/ui/components/button/button.component.scss @@ -109,10 +109,12 @@ } &--extra-small { + width: 70px; padding: 2px 10px; } &--small { + width: 70px; padding: 8px 24px; &--icon { diff --git a/projects/social_platform/src/app/ui/components/button/button.component.ts b/projects/social_platform/src/app/ui/components/button/button.component.ts index 0646c0f04..2e40eb57e 100644 --- a/projects/social_platform/src/app/ui/components/button/button.component.ts +++ b/projects/social_platform/src/app/ui/components/button/button.component.ts @@ -3,7 +3,6 @@ import { Component, Input, type OnInit } from "@angular/core"; import { LoaderComponent } from "../loader/loader.component"; import { CommonModule } from "@angular/common"; -import { IconComponent } from "@uilib"; /** * Универсальный компонент кнопки с различными стилями, состояниями и встроенной подсказкой. @@ -33,7 +32,7 @@ import { IconComponent } from "@uilib"; templateUrl: "./button.component.html", styleUrl: "./button.component.scss", standalone: true, - imports: [CommonModule, LoaderComponent, IconComponent], + imports: [CommonModule, LoaderComponent], }) export class ButtonComponent implements OnInit { constructor() {} diff --git a/projects/social_platform/src/app/utils/generate-options-list.ts b/projects/social_platform/src/app/utils/generate-options-list.ts index 6e078b047..f15bf93d6 100644 --- a/projects/social_platform/src/app/utils/generate-options-list.ts +++ b/projects/social_platform/src/app/utils/generate-options-list.ts @@ -1,6 +1,6 @@ /** @format */ -interface optionsListElement { +export interface optionsListElement { id: number; // порядковый номер в массиве value: number | string; // строка, которую будем показывать в UI label: string; // то же самое, что и value (можно использовать обе подписи) diff --git a/projects/social_platform/src/assets/icons/svg/subscribe-badge.svg b/projects/social_platform/src/assets/icons/svg/subscribe-badge.svg new file mode 100644 index 000000000..71851e596 --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/subscribe-badge.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/svg/suitcase.svg b/projects/social_platform/src/assets/icons/svg/suitcase.svg new file mode 100644 index 000000000..d0286abde --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/suitcase.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/svg/unsubscribe-badge.svg b/projects/social_platform/src/assets/icons/svg/unsubscribe-badge.svg new file mode 100644 index 000000000..755f05b3a --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/unsubscribe-badge.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg index fe9014178..da40dadbc 100644 --- a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg +++ b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/projects/social_platform/src/styles/_colors.scss b/projects/social_platform/src/styles/_colors.scss index a52891b1d..c1bd15bd3 100644 --- a/projects/social_platform/src/styles/_colors.scss +++ b/projects/social_platform/src/styles/_colors.scss @@ -24,7 +24,7 @@ --light-gray: #f9f9f9; --grey-button: #e5e5e5e5; --medium-grey-for-outline: #eee; - --grey-for-text: #a59fb9; + --grey-for-text: #827E80; // FUNCTIONAL --green: #88c9a1; diff --git a/projects/ui/src/lib/components/layout/sidebar/sidebar.component.ts b/projects/ui/src/lib/components/layout/sidebar/sidebar.component.ts index d1bc569df..cccb0b0fe 100644 --- a/projects/ui/src/lib/components/layout/sidebar/sidebar.component.ts +++ b/projects/ui/src/lib/components/layout/sidebar/sidebar.component.ts @@ -34,7 +34,6 @@ export interface NavItem { * - * * * \`\`\` */ @@ -43,16 +42,7 @@ export interface NavItem { templateUrl: "./sidebar.component.html", styleUrl: "./sidebar.component.scss", standalone: true, - imports: [ - RouterLink, - RouterLinkActive, - IconComponent, - ClickOutsideModule, - InviteManageCardComponent, - ProfileControlPanelComponent, - ProfileInfoComponent, - AsyncPipe, - ], + imports: [RouterLink, RouterLinkActive, IconComponent, ClickOutsideModule], }) export class SidebarComponent implements OnInit { /** Массив элементов навигации */ From 9ebb9eb63d731a4eb0b57aaf6f9831a0f2097736 Mon Sep 17 00:00:00 2001 From: Awakich Date: Sat, 13 Sep 2025 20:38:34 +0300 Subject: [PATCH 058/126] add styles for project-card & soon-card component --- .../project-card/project-card.component.scss | 15 ++++++++------- .../shared/soon-card/soon-card.component.scss | 6 +++--- projects/social_platform/src/styles/_colors.scss | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss b/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss index d7b2533fe..e1ecf912e 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss +++ b/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss @@ -25,8 +25,8 @@ flex-direction: column; align-items: center; justify-content: center; - text-align: center; padding-top: 40px; + text-align: center; } &__head { @@ -35,23 +35,24 @@ &__info { display: flex; + gap: 10px; align-items: center; justify-content: space-evenly; - gap: 10px; - &--vacancies, &--collaborators { + &--vacancies, + &--collaborators { display: flex; - align-items: center; gap: 4px; + align-items: center; color: var(--grey-for-text); } } &__subscribe-badge { display: block; - transform: translateY(10px); color: var(--accent); cursor: pointer; + transform: translateY(10px); } &__user { @@ -85,12 +86,12 @@ } &__project { - padding: 2px 24px; - border-radius: var(--rounded-xl); display: flex; flex-direction: column; align-items: center; justify-content: center; + padding: 2px 24px; + border-radius: var(--rounded-xl); &--text { color: var(--white); diff --git a/projects/social_platform/src/app/office/shared/soon-card/soon-card.component.scss b/projects/social_platform/src/app/office/shared/soon-card/soon-card.component.scss index 8c86c806c..232ad36fa 100644 --- a/projects/social_platform/src/app/office/shared/soon-card/soon-card.component.scss +++ b/projects/social_platform/src/app/office/shared/soon-card/soon-card.component.scss @@ -10,21 +10,21 @@ &__soon { position: relative; + display: grid; + place-items: center; margin-bottom: 4px; border: 0.5px solid var(--medium-grey-for-outline); border-radius: var(--rounded-lg); - display: grid; - place-items: center; } &__closed { display: flex; flex-direction: column; + grid-area: 1 / 1; gap: 2px; align-items: center; justify-content: center; padding: 24px 37px; - grid-area: 1 / 1; &--text { color: var(--dark-grey); diff --git a/projects/social_platform/src/styles/_colors.scss b/projects/social_platform/src/styles/_colors.scss index c1bd15bd3..bda5274dd 100644 --- a/projects/social_platform/src/styles/_colors.scss +++ b/projects/social_platform/src/styles/_colors.scss @@ -24,7 +24,7 @@ --light-gray: #f9f9f9; --grey-button: #e5e5e5e5; --medium-grey-for-outline: #eee; - --grey-for-text: #827E80; + --grey-for-text: #827e80; // FUNCTIONAL --green: #88c9a1; From b8b33b45cc19ffd24e0d49421c84656c2461c73a Mon Sep 17 00:00:00 2001 From: Awakich Date: Sat, 13 Sep 2025 23:14:48 +0300 Subject: [PATCH 059/126] change postion of profile control panel & fix height of project-card component --- .../src/app/office/office.component.html | 24 ++-- .../src/app/office/office.component.scss | 5 - .../project-card/project-card.component.html | 107 ++++++++++-------- .../project-card/project-card.component.scss | 105 ++++++++++++----- .../src/assets/icons/svg/bell.svg | 4 +- .../src/assets/icons/svg/logout3.svg | 3 + .../assets/icons/symbol/svg/sprite.css.svg | 2 +- .../profile-control-panel.component.html | 14 +-- .../profile-control-panel.component.scss | 68 +++-------- .../profile-info/profile-info.component.html | 12 +- .../profile-info/profile-info.component.scss | 6 +- 11 files changed, 179 insertions(+), 171 deletions(-) create mode 100644 projects/social_platform/src/assets/icons/svg/logout3.svg diff --git a/projects/social_platform/src/app/office/office.component.html b/projects/social_platform/src/app/office/office.component.html index 3e3862a09..d7fa3333b 100644 --- a/projects/social_platform/src/app/office/office.component.html +++ b/projects/social_platform/src/app/office/office.component.html @@ -7,17 +7,6 @@ logoSrc="/assets/images/shared/logo.svg" [navItems]="navItems" > - @if (user !== undefined && invites !== undefined) { - - }
Траектории @@ -25,17 +14,28 @@
PRO
- }
+ @if (user !== undefined && invites !== undefined) { + + }
+ }
wait diff --git a/projects/social_platform/src/app/office/office.component.scss b/projects/social_platform/src/app/office/office.component.scss index 2f0cdcdbd..5b7827577 100644 --- a/projects/social_platform/src/app/office/office.component.scss +++ b/projects/social_platform/src/app/office/office.component.scss @@ -27,12 +27,7 @@ &__body { flex-grow: 1; - padding: 10px 10px 0; overflow-y: auto; - - @include responsive.apply-desktop { - padding: 20px 20px 0; - } } &__header { diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.html b/projects/social_platform/src/app/office/shared/project-card/project-card.component.html index 806865f67..17c5f04af 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.html +++ b/projects/social_platform/src/app/office/shared/project-card/project-card.component.html @@ -15,51 +15,34 @@
- } @if (haveBadge) { @if (isSubscribed) { - - } @else { - - } } + } - -
-

Вы действительно хотите отписаться от проекта?

- -
- - Отписаться - - - Отменить - -
-
-
+
+ @if (haveBadge) { @if (isSubscribed) { + + } @else { + + } } +
+ +
-

{{ project.name }}

@if (industryService.industries | async; as industries) { @@ -73,11 +56,16 @@

Вы действительно хотите отписаться от пр }

+ @if (type === 'invite') { -

вас приглашает

-

{{ project.shortDescription }}

+
+

вас приглашает

+

{{ project.shortDescription }}

+
} @else { -

{{ project.shortDescription }}

+
+

{{ project.shortDescription }}

+
}
@@ -86,28 +74,53 @@

Вы действительно хотите отписаться от пр customTypographyClass="text-body-6" size="extra-small" class="card__project--text" - >проект + проект + } @else { принять + принять + отклонить + отклонить + }

+ +
+

Вы действительно хотите отписаться от проекта?

+ +
+ + Отписаться + + + Отменить + +
+
+
+

diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss b/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss index e1ecf912e..bca105ec3 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss +++ b/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss @@ -3,41 +3,23 @@ @use "styles/responsive"; .card { - height: 171px; - &__body { position: relative; + height: 170px; + display: flex; + flex-direction: column; padding: 15px 0 12px; background-color: var(--light-white); border: 0.5px solid var(--medium-for-outline); border-radius: var(--rounded-xl); } - &__photo { - position: absolute; - bottom: 72%; - left: 50%; - transform: translateX(-50%); - } - - &__content { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding-top: 40px; - text-align: center; - } - - &__head { - margin-bottom: 15px; - } - &__info { display: flex; gap: 10px; align-items: center; justify-content: space-evenly; + flex-shrink: 0; &--vacancies, &--collaborators { @@ -48,19 +30,44 @@ } } + &__badge { + position: absolute; + top: 30px; + left: 0px; + z-index: 1; + } + &__subscribe-badge { display: block; color: var(--accent); cursor: pointer; - transform: translateY(10px); } - &__user { - color: var(--grey-for-text); + &__photo { + position: absolute; + top: -35px; + left: 50%; + transform: translateX(-50%); + z-index: 2; + } - &--invite { - color: var(--accent); - } + &__content { + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + flex: 1; + padding-top: 40px; + text-align: center; + overflow: hidden; + } + + &__head { + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 8px; + flex-shrink: 0; } &__name { @@ -80,9 +87,28 @@ } &__description { - margin-bottom: 10px; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; color: var(--dark-grey); word-break: break-word; + flex: 1; + max-height: 20px; + overflow: hidden; + + &.invite-description { + justify-content: center; + } + } + + &__user { + color: var(--grey-for-text); + margin-bottom: 2px; + + &--invite { + color: var(--accent); + } } &__project { @@ -90,7 +116,7 @@ flex-direction: column; align-items: center; justify-content: center; - padding: 2px 24px; + margin-top: auto; border-radius: var(--rounded-xl); &--text { @@ -120,3 +146,22 @@ margin-top: 18px; } } + +.unsubscribe-modal { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + padding: 20px; + + h3 { + margin-bottom: 16px; + color: var(--black); + } + + &__buttons { + display: flex; + gap: 12px; + margin-top: 16px; + } +} diff --git a/projects/social_platform/src/assets/icons/svg/bell.svg b/projects/social_platform/src/assets/icons/svg/bell.svg index 16e745a68..452696228 100644 --- a/projects/social_platform/src/assets/icons/svg/bell.svg +++ b/projects/social_platform/src/assets/icons/svg/bell.svg @@ -1,3 +1,3 @@ - - + + diff --git a/projects/social_platform/src/assets/icons/svg/logout3.svg b/projects/social_platform/src/assets/icons/svg/logout3.svg new file mode 100644 index 000000000..b7c27c199 --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/logout3.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg index da40dadbc..e27f44e88 100644 --- a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg +++ b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.html b/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.html index 3975ec509..19f646a01 100644 --- a/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.html +++ b/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.html @@ -7,12 +7,12 @@ @if (hasNotifications || hasInvites) {
} - +

@if (showNotifications) {
-

Уведомления

+

Уведомления

    @for (invite of invites; track invite.id) {
  • @@ -26,17 +26,9 @@

    Уведомления

} - -
- @if (hasUnreads) { -
- } - -
-
- +
diff --git a/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.scss b/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.scss index cd316af9c..bb78dcf93 100644 --- a/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.scss +++ b/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.scss @@ -1,80 +1,42 @@ -:host { - display: block; - padding: 19px 20px 15px; - margin-top: auto; -} - .control-panel { display: flex; - flex-direction: column; align-items: center; - justify-content: center; + justify-content: flex-end; + margin: 20px auto; &__actions { - z-index: 2; display: flex; align-items: center; - justify-content: space-around; - width: 85%; - margin-bottom: -16px; + gap: 30px; + margin-right: 35px; } &__action { - display: flex; - align-items: center; - justify-content: center; - width: 41px; - height: 41px; - color: var(--accent); + color: var(--black); + opacity: 0.5; cursor: pointer; - background-color: var(--light-gray); - border: 4px solid var(--white); - border-radius: 50%; + + &:hover { + opacity: 0.8; + } } &__notifications { position: absolute; - right: 100%; + top: 0%; + width: 120px; + max-height: 240px; + transform: translate(-50%, 120%); z-index: 20; - width: 380px; - max-height: 600px; - transform: translate(100%, -100%); - } - - &__bell { - position: relative; - width: 20px; - - > .attention { - position: absolute; - top: 0; - right: 0; - width: 7px; - height: 7px; - background-color: var(--red); - border-radius: 50%; - } - } - - app-profile-info { - width: 100%; - padding: 26px 10px 10px; - background-color: var(--light-gray); - border-radius: var(--rounded-lg); } } .notifications { padding: 20px; - background-color: var(--white); + background-color: var(--light-white); border-radius: var(--rounded-md); box-shadow: 5px 5px 50px rgb(196 196 196 / 15%); - &__title { - margin-bottom: 18px; - color: var(--black); - } - &__invite { &:not(:last-child) { padding-bottom: 16px; diff --git a/projects/ui/src/lib/components/layout/profile-info/profile-info.component.html b/projects/ui/src/lib/components/layout/profile-info/profile-info.component.html index 509f3be5f..b001ea866 100644 --- a/projects/ui/src/lib/components/layout/profile-info/profile-info.component.html +++ b/projects/ui/src/lib/components/layout/profile-info/profile-info.component.html @@ -3,17 +3,11 @@ @if (user) {
- -
{{ user.firstName }} {{ user.lastName }}
+ +
+
-

}

+
@if (!projectRated()) { -
+
+
+

оценка проекта

+ +
@if (form | controlError: "required"; as error) {
@@ -71,39 +71,13 @@

О проекте

}
-
- - - Предыдущий проект - - - Оценить проект - - - Следующий проект - - -
+ оценить проект } @else {
@@ -116,7 +90,7 @@

О проекте

(click)="redoRating()" >
-
Проект оценен
+

Проект оценен

}
diff --git a/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.scss b/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.scss index e43ee4465..db6181553 100644 --- a/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.scss +++ b/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.scss @@ -3,165 +3,107 @@ @use "styles/responsive"; @use "styles/typography"; -:host { - min-width: 0; -} - .card { - border-radius: var(--rounded-xl); + display: grid; + grid-template-columns: 5fr 3fr; + gap: 20px; - &__upper { - padding: 14px; - background-color: var(--white); - border: 1px solid var(--accent); - border-radius: var(--rounded-xl); - - @include responsive.apply-desktop { - min-height: 550px; - } + &__wrapper { + padding: 24px; + background-color: var(--light-white); + border: 0.5px solid var(--dark-grey); + border-radius: var(--rounded-lg); } - &__heading { - position: relative; - z-index: 2; + &__inner { display: flex; - flex-direction: column; - gap: 20px; - border-radius: var(--rounded-xl); - - app-button ::ng-deep .button--inline { - min-height: 38px; - } - - @include responsive.apply-desktop { - flex-flow: row wrap; - gap: 10px; - align-items: flex-end; - justify-content: space-between; - padding: 47px 14px 47px 140px; - } + align-items: center; + justify-content: space-between; } - &__avatar { - position: absolute; - left: 50%; - z-index: 3; - display: block; - transform: translateX(-50%) translateY(15px); - - @include responsive.apply-desktop { - top: 50%; - left: 10px; - transform: translateY(-50%); - } + &__info { + display: flex; + align-items: center; + gap: 20px; } - &__info { + &__naming { display: flex; flex-direction: column; gap: 5px; - margin-top: 90px; - color: var(--dark-grey); + width: 60%; - @include responsive.apply-desktop { - flex-basis: 300px; - flex-flow: row wrap; - flex-grow: 9999; - gap: 30px; - margin-top: 0; + &--text { + color: var(--black); } } + &__industry { + display: inline-flex; + } + &__title { overflow: hidden; color: var(--black); text-align: center; text-overflow: ellipsis; - - @include typography.heading-4; - - @include responsive.apply-desktop { - text-align: unset; - - @include typography.heading-2; - } } - &__row { + &__rated { display: flex; - gap: 20px; + flex-direction: column; + gap: 15px; align-items: center; justify-content: center; - margin-top: 2px; - - @include responsive.apply-desktop { - justify-content: unset; - margin-top: 0; - } - } - - &__industry { - margin-right: 20px; - - @include responsive.apply-desktop { - margin-right: 40px; - } - } - - &__geo { - display: flex; - align-items: center; - - i { - margin-right: 5px; - } - } - - &__info-buttons { - flex-grow: 1; + padding-top: 15px; + padding-bottom: 10px; + color: var(--accent); } &__presentation { - display: block; - - i { - margin-left: 10px; + ::ng-deep { + app-button { + button { + padding-top: 4px; + padding-bottom: 4px; + } + } } } &__lower { display: flex; flex-direction: column; - gap: 15px; - padding: 14px; - margin-top: 25px; + gap: 14px; + + &--info { + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 0.5px solid var(--dark-grey); + padding-bottom: 10px; + margin-bottom: 10px; - @include responsive.apply-desktop { - gap: 24px; + p, i { + color: var(--accent); + } + } + ::ng-deep { app-button { - width: 50%; - max-width: 345px; + button { + padding-top: 4px; + padding-bottom: 4px; + } } } + &-icons { + display: flex; + gap: 10px; + align-items: center; + } } - &__rated { - display: flex; - flex-direction: column; - gap: 15px; - align-items: center; - justify-content: center; - padding-top: 15px; - padding-bottom: 10px; - color: var(--accent); - } - - &__lower-icons { - display: flex; - gap: 10px; - align-items: center; - } &__rated-icon { color: var(--green); @@ -171,34 +113,31 @@ color: var(--dark-grey); cursor: pointer; } - - &__buttons { - display: flex; - align-items: center; - justify-content: space-between; - } - - &__next-btn, - &__last-btn { - margin: 0 10px; - } } .about { - margin-top: 35px; + /* stylelint-disable value-no-vendor-prefix */ + margin-top: 10px; - @include responsive.apply-desktop { - padding: 0 10px 24px; - margin-top: unset; + &__title { + color: var(--accent); } - &__title { - margin-bottom: 12px; - color: var(--black); + &__top { + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 0.5px solid var(--dark-grey); + padding-bottom: 10px; + + i { + color: var(--accent); + } } - /* stylelint-disable value-no-vendor-prefix */ &__text { + margin-top: 10px; + p { display: -webkit-box; overflow: hidden; diff --git a/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.ts b/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.ts index fb42e3d42..beb241590 100644 --- a/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.ts +++ b/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.ts @@ -25,6 +25,7 @@ import { ProjectRatingComponent } from "@office/shared/project-rating/project-ra import { FormControl, ReactiveFormsModule } from "@angular/forms"; import { ProjectRatingService } from "@office/program/services/project-rating.service"; import { RouterLink } from "@angular/router"; +import { TagComponent } from "@ui/components/tag/tag.component"; /** * Компонент карточки оценки проекта @@ -79,6 +80,7 @@ import { RouterLink } from "@angular/router"; ProjectRatingComponent, ControlErrorPipe, RouterLink, + TagComponent, ], }) export class RatingCardComponent implements AfterViewInit, OnDestroy { @@ -99,27 +101,6 @@ export class RatingCardComponent implements AfterViewInit, OnDestroy { return this._project(); } - @Input({ required: true }) set projects(proj: ProjectRate[] | null) { - if (!proj) return; - this._projects.set(proj); - } - - get projects(): ProjectRate[] | null { - return this._projects(); - } - - @Input({ required: true }) set currentIndex(curIndx: number) { - if (!curIndx) return; - this._currentIndex.set(curIndx); - } - - get currentIndex(): number { - return this._currentIndex(); - } - - @Output() onNext: EventEmitter = new EventEmitter(); - @Output() onPrev: EventEmitter = new EventEmitter(); - @ViewChild("descEl") descEl?: ElementRef; _project = signal(null); @@ -187,12 +168,4 @@ export class RatingCardComponent implements AfterViewInit, OnDestroy { redoRating(): void { this.projectRated.set(false); } - - toggleRate(type: "next" | "prev"): void { - if (type === "next") { - this.onNext.emit(); - } else { - this.onPrev.emit(); - } - } } diff --git a/projects/social_platform/src/app/office/projects/edit/edit.component.scss b/projects/social_platform/src/app/office/projects/edit/edit.component.scss index 081e9b06d..d143d0c0c 100644 --- a/projects/social_platform/src/app/office/projects/edit/edit.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/edit.component.scss @@ -10,8 +10,8 @@ border-radius: var(--rounded-md); &__top { - position: fixed; - top: 5%; + position: sticky; + top: 10%; left: 6%; z-index: 10; display: flex; diff --git a/projects/social_platform/src/app/office/projects/list/list.component.scss b/projects/social_platform/src/app/office/projects/list/list.component.scss index 361776747..85d685865 100644 --- a/projects/social_platform/src/app/office/projects/list/list.component.scss +++ b/projects/social_platform/src/app/office/projects/list/list.component.scss @@ -30,7 +30,7 @@ display: grid; flex-grow: 1; grid-template-columns: 1fr; - row-gap: 40px; + row-gap: 50px; column-gap: 20px; align-items: flex-start; diff --git a/projects/social_platform/src/app/office/projects/projects-filter/projects-filter.component.html b/projects/social_platform/src/app/office/projects/projects-filter/projects-filter.component.html index 1ae5d46d0..5c98e3fb1 100644 --- a/projects/social_platform/src/app/office/projects/projects-filter/projects-filter.component.html +++ b/projects/social_platform/src/app/office/projects/projects-filter/projects-filter.component.html @@ -2,7 +2,9 @@
-
+
+

фильтры

+
-
+
+ +
- -
-

{{ user.userType | userRole | async }}

-

{{ user.firstName }} {{ user.lastName }}

- @if (user.speciality) { -

- {{ user.speciality }} - @if (user.speciality && user.birthday) { • } @if (user.birthday) { - {{ user.birthday | yearsFromBirthday }} - } -

- } + + +
+
+
+

{{ user.firstName }} {{ user.lastName }}

+ + @if (user.speciality) { +

+ {{ user.speciality }} + @if (user.speciality && user.birthday) { • } @if (user.birthday) { + {{ user.birthday | yearsFromBirthday }} + } +

+ } @if (user.skills && user.skills.length) { +
    + @for ( skill of user.skills.slice(0, 3); track skill.id ) { +
  • + {{ + skill.name.length > 10 ? skill.name.slice(0, 10) + " " + "..." : skill.name + }} +
  • + } +
+ } +
+
+ +
+ + профиль + +
- @if (user.skills && user.skills.length) { -
    - @for ( skill of user.skills.slice(0, user.skills.length >= 4 ? 4 : user.skills.length); track - skill.id ) { -
  • - {{ - skill.name.length > 10 ? skill.name.slice(0, 10) + " " + "..." : skill.name - }} -
  • - } @if (user.skills.length > 4) { - ... - } -
- }
diff --git a/projects/social_platform/src/app/office/shared/member-card/member-card.component.scss b/projects/social_platform/src/app/office/shared/member-card/member-card.component.scss index f14314bf5..146b05417 100644 --- a/projects/social_platform/src/app/office/shared/member-card/member-card.component.scss +++ b/projects/social_platform/src/app/office/shared/member-card/member-card.component.scss @@ -3,60 +3,175 @@ @use "styles/responsive"; .card { - height: 200px; - padding: 20px; - background-color: var(--white); - border: 1px solid var(--medium-grey-for-outline); - border-radius: var(--rounded-xl); - - &:hover { - box-shadow: 0 0 6px var(--gray); + &__body { + position: relative; + display: flex; + flex-direction: column; + height: 170px; + padding: 15px 0 12px; + background-color: var(--light-white); + border: 0.5px solid var(--medium-for-outline); + border-radius: var(--rounded-xl); } - &__body { + &__info { display: flex; + flex-shrink: 0; + gap: 50px; align-items: center; + justify-content: space-evenly; + + &--vacancies, + &--collaborators { + display: flex; + gap: 4px; + align-items: center; + color: var(--grey-for-text); + } } - &__info { - margin-left: 16px; + &__badge { + position: absolute; + top: 30px; + left: 0; + z-index: 1; } - &__status { - margin: 0 0 3px; - color: var(--dark-grey); + &__subscribe-badge { + display: block; + color: var(--accent); + cursor: pointer; + } + + &__photo { + position: absolute; + top: -35px; + left: 50%; + z-index: 2; + transform: translateX(-50%); + } + + &__content { + display: flex; + flex: 1; + flex-direction: column; + align-items: center; + justify-content: flex-start; + padding-top: 40px; + overflow: hidden; + text-align: center; + } + + &__head { + display: flex; + flex-direction: column; + flex-shrink: 0; + align-items: center; + margin-bottom: 8px; } &__name { max-width: 200px; - margin-bottom: 3px; overflow: hidden; color: var(--black); text-overflow: ellipsis; + white-space: nowrap; } &__additional-info { - display: flex; color: var(--accent); } - &__speciality { + &__industry { + padding: 2px 36px; + color: var(--light-white); + background-color: var(--green); + border-radius: var(--rounded-xl); + } + + &__description { display: flex; - max-width: 200px; + flex: 1; + flex-direction: column; + align-items: center; + max-height: 20px; overflow: hidden; + color: var(--dark-grey); + text-align: center; + word-break: break-word; + + &.invite-description { + justify-content: center; + } + } + + &__user { + margin-bottom: 2px; + color: var(--grey-for-text); + + &--invite { + color: var(--accent); + } + } + + &__project { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + margin-top: auto; + border-radius: var(--rounded-xl); + + &--text { + color: var(--white); + } } &__skills { + margin-top: 6px; display: flex; - flex-wrap: wrap; - margin-top: 16px; + flex-flow: wrap; + margin-bottom: 9px; } +} - &__skill { - margin-bottom: 5px; +.message-modal { + display: flex; + flex-direction: column; + align-items: center; + max-width: 402px; - &:not(:last-child) { - margin-right: 5px; - } + &__title { + margin: 18px 0; + color: var(--black); + text-align: center; + } + + &__text { + color: var(--dark-grey); + text-align: center; + } + + &__button { + margin-top: 18px; + } +} + +.unsubscribe-modal { + display: flex; + flex-direction: column; + align-items: center; + padding: 20px; + text-align: center; + + h3 { + margin-bottom: 16px; + color: var(--black); + } + + &__buttons { + display: flex; + gap: 12px; + margin-top: 16px; } } diff --git a/projects/social_platform/src/app/office/shared/member-card/member-card.component.ts b/projects/social_platform/src/app/office/shared/member-card/member-card.component.ts index 2dd4db880..66434d20a 100644 --- a/projects/social_platform/src/app/office/shared/member-card/member-card.component.ts +++ b/projects/social_platform/src/app/office/shared/member-card/member-card.component.ts @@ -7,6 +7,7 @@ import { UserRolePipe } from "@core/pipes/user-role.pipe"; import { TagComponent } from "@ui/components/tag/tag.component"; import { AsyncPipe } from "@angular/common"; import { AvatarComponent } from "@ui/components/avatar/avatar.component"; +import { ButtonComponent } from "@ui/components"; /** * Компонент карточки участника команды @@ -35,7 +36,14 @@ import { AvatarComponent } from "@ui/components/avatar/avatar.component"; templateUrl: "./member-card.component.html", styleUrl: "./member-card.component.scss", standalone: true, - imports: [AvatarComponent, TagComponent, AsyncPipe, UserRolePipe, YearsFromBirthdayPipe], + imports: [ + AvatarComponent, + TagComponent, + AsyncPipe, + UserRolePipe, + YearsFromBirthdayPipe, + ButtonComponent, + ], }) export class MemberCardComponent implements OnInit { constructor() {} diff --git a/projects/social_platform/src/app/office/shared/nav/nav.component.html b/projects/social_platform/src/app/office/shared/nav/nav.component.html index 877993e27..cdf606ec4 100644 --- a/projects/social_platform/src/app/office/shared/nav/nav.component.html +++ b/projects/social_platform/src/app/office/shared/nav/nav.component.html @@ -83,9 +83,8 @@

{{ title }}

- Траектории + Траектории
-
PRO
diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.html b/projects/social_platform/src/app/office/shared/project-card/project-card.component.html index 17c5f04af..62dcddbcd 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.html +++ b/projects/social_platform/src/app/office/shared/project-card/project-card.component.html @@ -1,6 +1,6 @@ -
+
@if (type === 'project') {
diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss b/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss index 65ec38fb1..dd6f17515 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss +++ b/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss @@ -17,7 +17,7 @@ &__info { display: flex; flex-shrink: 0; - gap: 10px; + gap: 50px; align-items: center; justify-content: space-evenly; diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.ts b/projects/social_platform/src/app/office/shared/project-card/project-card.component.ts index 2ca7d9c00..100c2b3d0 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.ts +++ b/projects/social_platform/src/app/office/shared/project-card/project-card.component.ts @@ -97,7 +97,10 @@ export class ProjectCardComponent implements OnInit { isUnsubscribeModalOpen = false; // Флаг модального окна отписки inviteErrorModal = false; // Флаг модального окна для ошибки приглашения - haveBadge = location.href.includes("/subscriptions") || location.href.includes("/all"); + haveBadge = + location.href.includes("/subscriptions") || + location.href.includes("/all") || + location.href.includes("/projects"); /** * Подписка на проект или открытие модального окна отписки diff --git a/projects/social_platform/src/app/office/shared/project-rating/components/range-criterion-input/range-criterion-input.component.html b/projects/social_platform/src/app/office/shared/project-rating/components/range-criterion-input/range-criterion-input.component.html index 265825103..ee71c8ee9 100644 --- a/projects/social_platform/src/app/office/shared/project-rating/components/range-criterion-input/range-criterion-input.component.html +++ b/projects/social_platform/src/app/office/shared/project-rating/components/range-criterion-input/range-criterion-input.component.html @@ -15,6 +15,6 @@ class="field__input" [class.field__input--error]="error" /> -  / - {{ max }} +  / + {{ max }}
diff --git a/projects/social_platform/src/app/office/shared/project-rating/components/range-criterion-input/range-criterion-input.component.scss b/projects/social_platform/src/app/office/shared/project-rating/components/range-criterion-input/range-criterion-input.component.scss index 6ea0c004f..6414880a0 100644 --- a/projects/social_platform/src/app/office/shared/project-rating/components/range-criterion-input/range-criterion-input.component.scss +++ b/projects/social_platform/src/app/office/shared/project-rating/components/range-criterion-input/range-criterion-input.component.scss @@ -10,15 +10,19 @@ .field { text-wrap: nowrap; + span { + color: var(--black); + } + &__input { - width: 24px; - height: 24px; + width: 15px; + height: 15px; color: transparent; text-align: center; text-shadow: 0 0 0 var(--black); background-color: var(--white); - border: 1px solid var(--gray); - border-radius: var(--rounded-md); + border: 0.5px solid var(--gray); + border-radius: var(--rounded-sm); outline: none; transition: all 0.2s; diff --git a/projects/social_platform/src/app/office/shared/project-rating/project-rating.component.html b/projects/social_platform/src/app/office/shared/project-rating/project-rating.component.html index fcd54ed39..78cbe6da0 100644 --- a/projects/social_platform/src/app/office/shared/project-rating/project-rating.component.html +++ b/projects/social_platform/src/app/office/shared/project-rating/project-rating.component.html @@ -3,20 +3,20 @@
@for (criterion of criteria; track $index) { @if (criterion.type === "int") {
- +
} @if (criterion.type === "bool") {
- +
} }
diff --git a/projects/social_platform/src/app/office/shared/project-rating/project-rating.component.scss b/projects/social_platform/src/app/office/shared/project-rating/project-rating.component.scss index 4eed67aa2..3bda0760e 100644 --- a/projects/social_platform/src/app/office/shared/project-rating/project-rating.component.scss +++ b/projects/social_platform/src/app/office/shared/project-rating/project-rating.component.scss @@ -9,22 +9,18 @@ width: 100%; &__columns { - display: grid; - grid-template-columns: 1fr; - grid-gap: 10px 8%; - align-content: center; - justify-content: space-around; - - @include responsive.apply-desktop { - grid-template-columns: repeat(2, 1fr min-content); - } + display: flex; + align-items: center; } &__field { - display: grid; - grid-template-columns: subgrid; - grid-column: span 2; + display: flex; + align-items: center; gap: 10px; + + label { + margin-top: 5px; + } } &__input { @@ -36,9 +32,9 @@ app-textarea { ::ng-deep .field__input { - padding: 10px; + color: var(--dark-grey); - @include typography.body-12; + @include typography.body-10; } } } diff --git a/projects/social_platform/src/app/office/shared/project-rating/project-rating.component.ts b/projects/social_platform/src/app/office/shared/project-rating/project-rating.component.ts index cafae8035..b7fb1ff1e 100644 --- a/projects/social_platform/src/app/office/shared/project-rating/project-rating.component.ts +++ b/projects/social_platform/src/app/office/shared/project-rating/project-rating.component.ts @@ -54,13 +54,11 @@ import { ErrorMessage } from "@error/models/error-message"; changeDetection: ChangeDetectionStrategy.OnPush, providers: [ { - // Регистрация как ControlValueAccessor для работы с формами provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ProjectRatingComponent), multi: true, }, { - // Регистрация как Validator для валидации provide: NG_VALIDATORS, useExisting: forwardRef(() => ProjectRatingComponent), multi: true, diff --git a/projects/social_platform/src/app/ui/components/select/select.component.html b/projects/social_platform/src/app/ui/components/select/select.component.html index f41f1beac..c99fb1126 100644 --- a/projects/social_platform/src/app/ui/components/select/select.component.html +++ b/projects/social_platform/src/app/ui/components/select/select.component.html @@ -25,8 +25,8 @@ [class.field__arrow--filpped]="isOpen" appIcon icon="arrow-no-body" - appWidth="18" - appHeight="9" + appWidth="10" + appHeight="8" > }
diff --git a/projects/social_platform/src/app/ui/components/select/select.component.ts b/projects/social_platform/src/app/ui/components/select/select.component.ts index 948a5d346..25d402153 100644 --- a/projects/social_platform/src/app/ui/components/select/select.component.ts +++ b/projects/social_platform/src/app/ui/components/select/select.component.ts @@ -58,7 +58,7 @@ export class SelectComponent implements ControlValueAccessor { /** Массив доступных опций */ @Input({ required: true }) options: { - value: string | number; + value: string | number | boolean | null; label: string; id: number; }[] = []; @@ -148,7 +148,7 @@ export class SelectComponent implements ControlValueAccessor { this.disabled = isDisabled; } - onChange: (value: string | number) => void = () => {}; + onChange: (value: string | number | null | boolean) => void = () => {}; registerOnChange(fn: any) { this.onChange = fn; @@ -179,7 +179,7 @@ export class SelectComponent implements ControlValueAccessor { } /** Получение значения по ID опции */ - getValue(optionId: number): string | number | null | undefined { + getValue(optionId: number): string | number | null | undefined | boolean { return this.options.find(el => el.id === optionId)?.value; } diff --git a/projects/social_platform/src/app/ui/components/tag/tag.component.html b/projects/social_platform/src/app/ui/components/tag/tag.component.html index 99509af69..a6eac461d 100644 --- a/projects/social_platform/src/app/ui/components/tag/tag.component.html +++ b/projects/social_platform/src/app/ui/components/tag/tag.component.html @@ -1,11 +1,14 @@
diff --git a/projects/social_platform/src/app/ui/components/tag/tag.component.scss b/projects/social_platform/src/app/ui/components/tag/tag.component.scss index 9c307d574..5240f4e02 100644 --- a/projects/social_platform/src/app/ui/components/tag/tag.component.scss +++ b/projects/social_platform/src/app/ui/components/tag/tag.component.scss @@ -11,24 +11,70 @@ white-space: nowrap; border-radius: var(--rounded-xxl); - &--primary { + &--inline { color: var(--white); - background-color: var(--accent); + border: 0.5px solid transparent; + + &.tag--primary { + color: var(--white); + background-color: var(--accent); + } + + &.tag--secondary { + background: var(--dark-grey); + border: 0.5px solid var(--grey-for-text); + } + + &.tag--accent { + color: var(--white); + background-color: var(--accent); + } + + &.tag--complete { + color: var(--white); + background-color: var(--green); + } + + &.tag--soft { + color: var(--gold); + border: transparent; + background: var(--gold); + } } - &--secondary { - background: transparent; + &--outline { border: 0.5px solid var(--accent); - } + background: transparent; + color: var(--accent); - &--accent { - color: var(--white); - background-color: var(--accent); - } + &.tag--primary { + color: var(--accent); + border: 0.5px solid var(--accent); + } - &--complete { - color: var(--white); - background-color: var(--green); + &.tag--secondary { + background: transparent; + color: var(--grey-for-text); + border: 0.5px solid var(--dark-grey); + } + + &.tag--accent { + color: var(--accent); + background: transparent; + border: 0.5px solid var(--accent); + } + + &.tag--complete { + color: var(--green); + background: transparent; + border: 0.5px solid var(--green); + } + + &.tag--soft { + color: var(--gold); + border: 0.5px solid var(--gold); + background: transparent; + } } ::ng-deep { diff --git a/projects/social_platform/src/app/ui/components/tag/tag.component.ts b/projects/social_platform/src/app/ui/components/tag/tag.component.ts index 6c53a90a1..44cc9d36d 100644 --- a/projects/social_platform/src/app/ui/components/tag/tag.component.ts +++ b/projects/social_platform/src/app/ui/components/tag/tag.component.ts @@ -25,7 +25,10 @@ export class TagComponent implements OnInit { constructor() {} /** Цветовая схема тега */ - @Input() color: "primary" | "secondary" | "accent" | "complete" = "primary"; + @Input() color: "primary" | "secondary" | "accent" | "complete" | "soft" = "primary"; + + /** Стиль отображения */ + @Input() appearance: "inline" | "outline" = "inline"; ngOnInit(): void {} } diff --git a/projects/social_platform/src/app/ui/components/tooltip/tooltip.component.html b/projects/social_platform/src/app/ui/components/tooltip/tooltip.component.html index 6dfc50e01..3708d7ed1 100644 --- a/projects/social_platform/src/app/ui/components/tooltip/tooltip.component.html +++ b/projects/social_platform/src/app/ui/components/tooltip/tooltip.component.html @@ -10,7 +10,7 @@ (mouseleave)="hide.emit()" >
- + + diff --git a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg index e27f44e88..5c733b731 100644 --- a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg +++ b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/projects/social_platform/src/styles/_colors.scss b/projects/social_platform/src/styles/_colors.scss index bda5274dd..cfbc1700b 100644 --- a/projects/social_platform/src/styles/_colors.scss +++ b/projects/social_platform/src/styles/_colors.scss @@ -12,8 +12,8 @@ --accent-light: #9a80e6; // GOLD - --gold: #f6ff8b; - --gold-dark: #f7cf4d; + --gold: #E5B25D; + --gold-dark: #c69849; // GRAY --light-white: #fff; diff --git a/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.scss b/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.scss index 4b803403e..133709d9d 100644 --- a/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.scss +++ b/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.scss @@ -3,6 +3,9 @@ align-items: center; justify-content: flex-end; margin: 15px auto; + position: sticky; + top: 15px; + z-index: 1000; &__actions { display: flex; @@ -23,25 +26,11 @@ &__notifications { position: absolute; - top: 0%; + top: 100%; + right: 0; z-index: 20; width: 120px; max-height: 240px; - transform: translate(-50%, 120%); - } -} - -.notifications { - padding: 20px; - background-color: var(--light-white); - border-radius: var(--rounded-md); - box-shadow: 5px 5px 50px rgb(196 196 196 / 15%); - - &__invite { - &:not(:last-child) { - padding-bottom: 16px; - margin-bottom: 16px; - border-bottom: 1px solid var(--gray); - } + margin-top: 10px; } } diff --git a/projects/ui/src/lib/components/layout/sidebar/sidebar.component.html b/projects/ui/src/lib/components/layout/sidebar/sidebar.component.html index 3a055a467..a1a1ff179 100644 --- a/projects/ui/src/lib/components/layout/sidebar/sidebar.component.html +++ b/projects/ui/src/lib/components/layout/sidebar/sidebar.component.html @@ -21,7 +21,7 @@ [routerLinkActive]="item.isExternal ? '' : 'sidebar-nav__item--active'" [class.sidebar-nav__item--active]="item.isExternal && item.isActive" > - + {{ item.name }} } diff --git a/projects/ui/src/lib/components/layout/sidebar/sidebar.component.scss b/projects/ui/src/lib/components/layout/sidebar/sidebar.component.scss index 0ca86eec2..f8d74a7a1 100644 --- a/projects/ui/src/lib/components/layout/sidebar/sidebar.component.scss +++ b/projects/ui/src/lib/components/layout/sidebar/sidebar.component.scss @@ -1,8 +1,10 @@ .sidebar { display: flex; flex-direction: column; + position: fixed; height: 100%; padding-top: 20px; + width: 150px; background-color: var(--white); &__logo { From 5006b5102d54b1a8c4de373d507cc8a519d2ccd5 Mon Sep 17 00:00:00 2001 From: Awakich Date: Tue, 16 Sep 2025 01:54:36 +0300 Subject: [PATCH 064/126] change styles for members, projects, rating & tag component --- .../src/app/office/office.component.scss | 4 ++-- .../program/detail/detail/detail.component.scss | 6 +++--- .../office/program/detail/main/main.component.scss | 2 +- .../program/detail/members/members.component.scss | 4 ++-- .../program/detail/projects/projects.component.scss | 4 ++-- .../rate-projects/rate-projects.component.scss | 4 ++-- .../shared/program-card/program-card.component.scss | 4 ++-- .../shared/rating-card/rating-card.component.scss | 13 +++++++------ .../projects-filter/projects-filter.component.scss | 4 ++-- .../src/app/office/projects/projects.component.scss | 2 +- .../shared/member-card/member-card.component.scss | 2 +- .../project-rating/project-rating.component.scss | 2 +- .../src/app/ui/components/tag/tag.component.scss | 10 +++++----- projects/social_platform/src/styles/_colors.scss | 2 +- .../profile-control-panel.component.scss | 6 +++--- .../layout/sidebar/sidebar.component.scss | 4 ++-- 16 files changed, 37 insertions(+), 36 deletions(-) diff --git a/projects/social_platform/src/app/office/office.component.scss b/projects/social_platform/src/app/office/office.component.scss index 43e0e6fed..9738a8301 100644 --- a/projects/social_platform/src/app/office/office.component.scss +++ b/projects/social_platform/src/app/office/office.component.scss @@ -32,8 +32,8 @@ display: flex; flex-grow: 1; justify-content: center; - overflow-y: auto; padding: 0 200px; + overflow-y: auto; @media (max-width: 1600px) { padding: 0 150px; @@ -129,8 +129,8 @@ .container { width: 100%; max-width: 1040px; - margin: 0 auto; padding: 0 200px; + margin: 0 auto; @media (max-width: 1600px) { padding: 0 150px; diff --git a/projects/social_platform/src/app/office/program/detail/detail/detail.component.scss b/projects/social_platform/src/app/office/program/detail/detail/detail.component.scss index afcdefd2e..244ef2f1b 100644 --- a/projects/social_platform/src/app/office/program/detail/detail/detail.component.scss +++ b/projects/social_platform/src/app/office/program/detail/detail/detail.component.scss @@ -67,9 +67,9 @@ &__actions { display: grid; + grid-template-columns: repeat(5, 2fr); gap: 20px; align-items: center; - grid-template-columns: repeat(5, 2fr); } &__presentation { @@ -80,11 +80,11 @@ color: var(--accent); } - app-tooltip { - z-index: 1000; + app-tooltip { position: absolute; right: -11%; bottom: 48%; + z-index: 1000; } } diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.scss b/projects/social_platform/src/app/office/program/detail/main/main.component.scss index 9628025bc..07c49a20f 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.scss +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.scss @@ -206,10 +206,10 @@ max-height: calc(100vh - 40px); i { - cursor: pointer; position: absolute; top: 0; right: 0; + cursor: pointer; } &__top { diff --git a/projects/social_platform/src/app/office/program/detail/members/members.component.scss b/projects/social_platform/src/app/office/program/detail/members/members.component.scss index 31a814eb1..3a19b51a3 100644 --- a/projects/social_platform/src/app/office/program/detail/members/members.component.scss +++ b/projects/social_platform/src/app/office/program/detail/members/members.component.scss @@ -14,10 +14,10 @@ &__outlet { display: flex; flex-direction: column; + flex-grow: 1; gap: 20px; width: 100%; margin-top: 10px; - flex-grow: 1; } &__filter { @@ -51,8 +51,8 @@ display: flex; flex-direction: column; gap: 20px; - margin-top: 20px; align-items: center; + margin-top: 20px; } &__left { diff --git a/projects/social_platform/src/app/office/program/detail/projects/projects.component.scss b/projects/social_platform/src/app/office/program/detail/projects/projects.component.scss index 31a814eb1..3a19b51a3 100644 --- a/projects/social_platform/src/app/office/program/detail/projects/projects.component.scss +++ b/projects/social_platform/src/app/office/program/detail/projects/projects.component.scss @@ -14,10 +14,10 @@ &__outlet { display: flex; flex-direction: column; + flex-grow: 1; gap: 20px; width: 100%; margin-top: 10px; - flex-grow: 1; } &__filter { @@ -51,8 +51,8 @@ display: flex; flex-direction: column; gap: 20px; - margin-top: 20px; align-items: center; + margin-top: 20px; } &__left { diff --git a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.scss b/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.scss index f85de3e70..8f0b08f08 100644 --- a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.scss +++ b/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.scss @@ -13,10 +13,10 @@ &__outlet { display: flex; flex-direction: column; + flex-grow: 1; gap: 20px; width: 100%; margin-top: 10px; - flex-grow: 1; } &__filter { @@ -44,8 +44,8 @@ display: flex; flex-direction: column; gap: 20px; - margin-top: 20px; align-items: center; + margin-top: 20px; } } diff --git a/projects/social_platform/src/app/office/program/shared/program-card/program-card.component.scss b/projects/social_platform/src/app/office/program/shared/program-card/program-card.component.scss index 333a2f35f..c8162485f 100644 --- a/projects/social_platform/src/app/office/program/shared/program-card/program-card.component.scss +++ b/projects/social_platform/src/app/office/program/shared/program-card/program-card.component.scss @@ -13,12 +13,12 @@ position: relative; z-index: 10; flex-shrink: 0; + width: 123px; + height: 123px; padding: 12px; background: var(--white); border: 0.5px solid var(--dark-grey); border-radius: 50%; - width: 123px; - height: 123px; } &__info { diff --git a/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.scss b/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.scss index db6181553..8bd728a18 100644 --- a/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.scss +++ b/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.scss @@ -23,8 +23,8 @@ &__info { display: flex; - align-items: center; gap: 20px; + align-items: center; } &__naming { @@ -75,16 +75,17 @@ display: flex; flex-direction: column; gap: 14px; - + &--info { display: flex; align-items: center; justify-content: space-between; - border-bottom: 0.5px solid var(--dark-grey); padding-bottom: 10px; margin-bottom: 10px; + border-bottom: 0.5px solid var(--dark-grey); - p, i { + p, + i { color: var(--accent); } } @@ -97,6 +98,7 @@ } } } + &-icons { display: flex; gap: 10px; @@ -104,7 +106,6 @@ } } - &__rated-icon { color: var(--green); } @@ -127,8 +128,8 @@ display: flex; align-items: center; justify-content: space-between; - border-bottom: 0.5px solid var(--dark-grey); padding-bottom: 10px; + border-bottom: 0.5px solid var(--dark-grey); i { color: var(--accent); diff --git a/projects/social_platform/src/app/office/projects/projects-filter/projects-filter.component.scss b/projects/social_platform/src/app/office/projects/projects-filter/projects-filter.component.scss index 5447d911d..87eec5d64 100644 --- a/projects/social_platform/src/app/office/projects/projects-filter/projects-filter.component.scss +++ b/projects/social_platform/src/app/office/projects/projects-filter/projects-filter.component.scss @@ -91,8 +91,8 @@ margin-bottom: 6px; color: var(--black); cursor: pointer; - - ::ng-deep { + + ::ng-deep { app-select { .field__input, li { diff --git a/projects/social_platform/src/app/office/projects/projects.component.scss b/projects/social_platform/src/app/office/projects/projects.component.scss index ae76d27be..4a0b7fe2c 100644 --- a/projects/social_platform/src/app/office/projects/projects.component.scss +++ b/projects/social_platform/src/app/office/projects/projects.component.scss @@ -14,10 +14,10 @@ &__outlet { display: flex; flex-direction: column; + flex-grow: 1; gap: 20px; width: 100%; margin-top: 10px; - flex-grow: 1; } &__create { diff --git a/projects/social_platform/src/app/office/shared/member-card/member-card.component.scss b/projects/social_platform/src/app/office/shared/member-card/member-card.component.scss index 146b05417..d96f9f83c 100644 --- a/projects/social_platform/src/app/office/shared/member-card/member-card.component.scss +++ b/projects/social_platform/src/app/office/shared/member-card/member-card.component.scss @@ -128,9 +128,9 @@ } &__skills { - margin-top: 6px; display: flex; flex-flow: wrap; + margin-top: 6px; margin-bottom: 9px; } } diff --git a/projects/social_platform/src/app/office/shared/project-rating/project-rating.component.scss b/projects/social_platform/src/app/office/shared/project-rating/project-rating.component.scss index 3bda0760e..5a6ac923a 100644 --- a/projects/social_platform/src/app/office/shared/project-rating/project-rating.component.scss +++ b/projects/social_platform/src/app/office/shared/project-rating/project-rating.component.scss @@ -15,8 +15,8 @@ &__field { display: flex; - align-items: center; gap: 10px; + align-items: center; label { margin-top: 5px; diff --git a/projects/social_platform/src/app/ui/components/tag/tag.component.scss b/projects/social_platform/src/app/ui/components/tag/tag.component.scss index 5240f4e02..526b5dc60 100644 --- a/projects/social_platform/src/app/ui/components/tag/tag.component.scss +++ b/projects/social_platform/src/app/ui/components/tag/tag.component.scss @@ -37,15 +37,15 @@ &.tag--soft { color: var(--gold); - border: transparent; background: var(--gold); + border: transparent; } } &--outline { - border: 0.5px solid var(--accent); - background: transparent; color: var(--accent); + background: transparent; + border: 0.5px solid var(--accent); &.tag--primary { color: var(--accent); @@ -53,8 +53,8 @@ } &.tag--secondary { - background: transparent; color: var(--grey-for-text); + background: transparent; border: 0.5px solid var(--dark-grey); } @@ -72,8 +72,8 @@ &.tag--soft { color: var(--gold); - border: 0.5px solid var(--gold); background: transparent; + border: 0.5px solid var(--gold); } } diff --git a/projects/social_platform/src/styles/_colors.scss b/projects/social_platform/src/styles/_colors.scss index cfbc1700b..49444a3ab 100644 --- a/projects/social_platform/src/styles/_colors.scss +++ b/projects/social_platform/src/styles/_colors.scss @@ -12,7 +12,7 @@ --accent-light: #9a80e6; // GOLD - --gold: #E5B25D; + --gold: #e5b25d; --gold-dark: #c69849; // GRAY diff --git a/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.scss b/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.scss index 133709d9d..25381e150 100644 --- a/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.scss +++ b/projects/ui/src/lib/components/layout/profile-control-panel/profile-control-panel.component.scss @@ -1,11 +1,11 @@ .control-panel { + position: sticky; + top: 15px; + z-index: 1000; display: flex; align-items: center; justify-content: flex-end; margin: 15px auto; - position: sticky; - top: 15px; - z-index: 1000; &__actions { display: flex; diff --git a/projects/ui/src/lib/components/layout/sidebar/sidebar.component.scss b/projects/ui/src/lib/components/layout/sidebar/sidebar.component.scss index f8d74a7a1..4d134c4d4 100644 --- a/projects/ui/src/lib/components/layout/sidebar/sidebar.component.scss +++ b/projects/ui/src/lib/components/layout/sidebar/sidebar.component.scss @@ -1,10 +1,10 @@ .sidebar { + position: fixed; display: flex; flex-direction: column; - position: fixed; + width: 150px; height: 100%; padding-top: 20px; - width: 150px; background-color: var(--white); &__logo { From a9b6d3def8d3725fd60a7321ad9f1446e08b2706 Mon Sep 17 00:00:00 2001 From: Awakich Date: Tue, 16 Sep 2025 19:15:19 +0300 Subject: [PATCH 065/126] change design of rating-project page, dashboard page in projects & project-card, rating-card component & add new resolvers, services for dashboard projects --- .../src/app/office/office.routes.ts | 2 - .../detail/detail/detail.component.html | 5 +- .../detail/detail/detail.component.scss | 3 +- .../program/detail/detail/detail.component.ts | 3 +- .../program/detail/main/main.component.scss | 19 +- .../rate-projects.component.html | 12 +- .../rate-projects.component.scss | 18 ++ .../rate-projects/rate-projects.component.ts | 6 +- .../app/office/program/models/project-rate.ts | 2 +- .../rating-card/rating-card.component.html | 71 ++++-- .../rating-card/rating-card.component.scss | 62 +++-- .../rating-card/rating-card.component.ts | 72 +++++- .../dashboard/dashboard.component.html | 23 ++ .../dashboard/dashboard.component.scss | 7 + .../projects/dashboard/dashboard.component.ts | 42 ++++ .../dashboardItem.component.html | 31 +++ .../dashboardItem.component.scss | 48 ++++ .../dashboardItem/dashboardItem.component.ts | 47 ++++ .../office/projects/list/list.component.ts | 12 +- .../office/projects/projects.component.html | 80 +++--- .../office/projects/projects.component.scss | 45 +++- .../app/office/projects/projects.component.ts | 41 ++-- .../app/office/projects/projects.resolver.ts | 28 ++- .../app/office/projects/projects.routes.ts | 12 +- .../projects/services/projects.service.ts | 26 ++ .../project-card/project-card.component.html | 227 ++++++++++-------- .../project-card/project-card.component.scss | 34 +++ .../project-card/project-card.component.ts | 186 ++++++++++---- .../range-criterion-input.component.html | 2 +- .../range-criterion-input.component.scss | 2 - .../project-rating.component.scss | 4 + .../src/app/utils/inviteToProjectMapper.ts | 17 ++ .../src/assets/icons/svg/feed.svg | 4 +- .../src/assets/icons/svg/message.svg | 6 +- .../src/assets/icons/svg/people-bold.svg | 9 +- .../src/assets/icons/svg/projects-filled.svg | 6 - .../src/assets/icons/svg/projects.svg | 7 +- .../src/assets/icons/svg/trajectories.svg | 3 + .../assets/icons/symbol/svg/sprite.css.svg | 2 +- .../images/projects/shared/add-project.svg | 4 + .../assets/images/projects/shared/arrow.svg | 3 + 41 files changed, 888 insertions(+), 345 deletions(-) create mode 100644 projects/social_platform/src/app/office/projects/dashboard/dashboard.component.html create mode 100644 projects/social_platform/src/app/office/projects/dashboard/dashboard.component.scss create mode 100644 projects/social_platform/src/app/office/projects/dashboard/dashboard.component.ts create mode 100644 projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.html create mode 100644 projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.scss create mode 100644 projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.ts create mode 100644 projects/social_platform/src/app/office/projects/services/projects.service.ts create mode 100644 projects/social_platform/src/app/utils/inviteToProjectMapper.ts delete mode 100644 projects/social_platform/src/assets/icons/svg/projects-filled.svg create mode 100644 projects/social_platform/src/assets/icons/svg/trajectories.svg create mode 100644 projects/social_platform/src/assets/images/projects/shared/add-project.svg create mode 100644 projects/social_platform/src/assets/images/projects/shared/arrow.svg diff --git a/projects/social_platform/src/app/office/office.routes.ts b/projects/social_platform/src/app/office/office.routes.ts index cda648e88..6d68b3ce2 100644 --- a/projects/social_platform/src/app/office/office.routes.ts +++ b/projects/social_platform/src/app/office/office.routes.ts @@ -7,8 +7,6 @@ import { MembersComponent } from "./members/members.component"; import { MembersResolver } from "./members/members.resolver"; import { VacancySendComponent } from "./vacancy/send/send.component"; import { OfficeResolver } from "./office.resolver"; -import { MentorsComponent } from "./mentors/mentors.component"; -import { MentorsResolver } from "./mentors/mentors.resolver"; /** * Конфигурация маршрутов для модуля офиса diff --git a/projects/social_platform/src/app/office/program/detail/detail/detail.component.html b/projects/social_platform/src/app/office/program/detail/detail/detail.component.html index 9670a783c..f99182d79 100644 --- a/projects/social_platform/src/app/office/program/detail/detail/detail.component.html +++ b/projects/social_platform/src/app/office/program/detail/detail/detail.component.html @@ -48,7 +48,7 @@ (click)="toggleSubmitProjectModal()" customTypographyClass="text-body-12 bar__add-project" > - подать проект + подать проект } @else if (program.isUserManager) { @@ -93,8 +93,9 @@ (show)="showTooltip()" (hide)="hideTooltip()" > - } + } @if (!program.isUserManager) { + } diff --git a/projects/social_platform/src/app/office/program/detail/detail/detail.component.scss b/projects/social_platform/src/app/office/program/detail/detail/detail.component.scss index 244ef2f1b..ece475858 100644 --- a/projects/social_platform/src/app/office/program/detail/detail/detail.component.scss +++ b/projects/social_platform/src/app/office/program/detail/detail/detail.component.scss @@ -68,7 +68,6 @@ &__actions { display: grid; grid-template-columns: repeat(5, 2fr); - gap: 20px; align-items: center; } @@ -82,7 +81,7 @@ app-tooltip { position: absolute; - right: -11%; + right: -5%; bottom: 48%; z-index: 1000; } diff --git a/projects/social_platform/src/app/office/program/detail/detail/detail.component.ts b/projects/social_platform/src/app/office/program/detail/detail/detail.component.ts index e8f2903fa..a3d342fcd 100644 --- a/projects/social_platform/src/app/office/program/detail/detail/detail.component.ts +++ b/projects/social_platform/src/app/office/program/detail/detail/detail.component.ts @@ -4,7 +4,7 @@ import { Component, inject, OnDestroy, OnInit, signal } from "@angular/core"; import { NavService } from "@services/nav.service"; import { ActivatedRoute, Router, RouterModule, RouterOutlet } from "@angular/router"; import { BarComponent, ButtonComponent, InputComponent } from "@ui/components"; -import { BackComponent } from "@uilib"; +import { BackComponent, IconComponent } from "@uilib"; import { AvatarComponent } from "@ui/components/avatar/avatar.component"; import { ModalComponent } from "@ui/components/modal/modal.component"; import { CommonModule, Location } from "@angular/common"; @@ -61,6 +61,7 @@ import { TooltipComponent } from "@ui/components/tooltip/tooltip.component"; RouterModule, ButtonComponent, InputComponent, + IconComponent, AvatarComponent, ModalComponent, AutosizeModule, diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.scss b/projects/social_platform/src/app/office/program/detail/main/main.component.scss index 07c49a20f..21007a1cb 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.scss +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.scss @@ -16,14 +16,14 @@ &__right { display: flex; flex-direction: column; - gap: 20px; } &__section { padding: 24px; - background-color: var(--white); + background-color: var(--light-white); border: 0.5px solid var(--medium-grey-for-outline); border-radius: var(--rounded-lg); + margin-bottom: 14px; } &__info { @@ -63,21 +63,6 @@ } } -.bar__add-project { - display: flex; - align-items: center; - cursor: pointer; - - i { - display: block; - margin-left: 6px; - } -} - -.bar__add-project-text { - display: inline; -} - .about { padding: 24px; background-color: var(--light-white); diff --git a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.html b/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.html index fa631f91e..8e4872807 100644 --- a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.html +++ b/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.html @@ -25,14 +25,10 @@

фильтр

- @for (tag of ratingOptionsList; track $index) { -
- -

{{ tag.label }}

-
- } +
diff --git a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.scss b/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.scss index 8f0b08f08..636445836 100644 --- a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.scss +++ b/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.scss @@ -5,6 +5,7 @@ display: grid; grid-template-columns: 8fr 2fr; padding-bottom: 100px; + gap: 20px; &__search { flex-grow: 1; @@ -61,6 +62,23 @@ position: static; } + &__controls { + margin-top: 18px; + display: flex; + flex-direction: column; + gap: 14px; + } + + &__tags { + display: flex; + align-items: center; + gap: 12px; + + p { + color: var(--grey-for-text); + } + } + &__overlay { position: absolute; top: 0; diff --git a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.ts b/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.ts index 12746b152..3cc9124c2 100644 --- a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.ts +++ b/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.ts @@ -178,12 +178,12 @@ export class RateProjectsComponent implements OnInit, OnDestroy { this.subscriptions$.forEach($ => $?.unsubscribe()); } - setValue(event: Event, tag: boolean | null) { + setValue(event: Event) { event.stopPropagation(); - this.filterForm.get("filterTag")?.setValue(tag); + this.filterForm.get("filterTag")?.setValue(!this.filterForm.get("filterTag")?.value); this.router.navigate([], { - queryParams: { is_rated_by_expert: tag }, + queryParams: { is_rated_by_expert: this.filterForm.get("filterTag")?.value }, relativeTo: this.route, queryParamsHandling: "merge", }); diff --git a/projects/social_platform/src/app/office/program/models/project-rate.ts b/projects/social_platform/src/app/office/program/models/project-rate.ts index 0984a29ef..a679a43d1 100644 --- a/projects/social_platform/src/app/office/program/models/project-rate.ts +++ b/projects/social_platform/src/app/office/program/models/project-rate.ts @@ -32,5 +32,5 @@ export interface ProjectRate { viewsCount: number; industry: number; criterias: ProjectRatingCriterion[]; - isScored: boolean; + scored: boolean; } diff --git a/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.html b/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.html index b765b05bd..03bb316ff 100644 --- a/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.html +++ b/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.html @@ -57,9 +57,9 @@
+
- @if (!projectRated()) { -
+

оценка проекта

@@ -71,28 +71,57 @@
}
- оценить проект - } @else { + + @if (showRatingForm || showRatedWithEdit || showConfirmedState) {
-
- - -
-

Проект оценен

+ + {{ showRatingForm ? "оценить проект" : "проект оценен" }} + + + @if (showRatedWithEdit) { + + + + }
} + + +
+
+

подтвердите оценку

+ +
+ + + подтверждаю + +
+
} diff --git a/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.scss b/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.scss index 8bd728a18..22b985814 100644 --- a/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.scss +++ b/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.scss @@ -11,7 +11,7 @@ &__wrapper { padding: 24px; background-color: var(--light-white); - border: 0.5px solid var(--dark-grey); + border: 0.5px solid var(--grey-button); border-radius: var(--rounded-lg); } @@ -51,13 +51,17 @@ &__rated { display: flex; - flex-direction: column; - gap: 15px; align-items: center; - justify-content: center; - padding-top: 15px; - padding-bottom: 10px; - color: var(--accent); + gap: 4px; + + &--icon { + ::ng-deep { + button { + width: 50px; + padding: 8px !important; + } + } + } } &__presentation { @@ -98,21 +102,6 @@ } } } - - &-icons { - display: flex; - gap: 10px; - align-items: center; - } - } - - &__rated-icon { - color: var(--green); - } - - &__redo-rating-icon { - color: var(--dark-grey); - cursor: pointer; } } @@ -174,3 +163,32 @@ cursor: pointer; } } + +.cancel { + position: relative; + display: block; + width: 350px; + max-height: calc(100vh - 40px); + + i { + color: var(--green); + padding: 36px 0px; + } + + &__top { + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 10px; + } + + &__title { + text-align: center; + } + + &__text { + width: 40%; + color: var(--dark-grey); + text-align: center; + } +} diff --git a/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.ts b/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.ts index beb241590..e835a5d7e 100644 --- a/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.ts +++ b/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.ts @@ -8,6 +8,7 @@ import { EventEmitter, Input, OnDestroy, + OnInit, Output, signal, ViewChild, @@ -26,6 +27,7 @@ import { FormControl, ReactiveFormsModule } from "@angular/forms"; import { ProjectRatingService } from "@office/program/services/project-rating.service"; import { RouterLink } from "@angular/router"; import { TagComponent } from "@ui/components/tag/tag.component"; +import { ModalComponent } from "@ui/components/modal/modal.component"; /** * Компонент карточки оценки проекта @@ -61,7 +63,9 @@ import { TagComponent } from "@ui/components/tag/tag.component"; * @property {Signal} submitLoading - Состояние загрузки при отправке * @property {Signal} readFullDescription - Развернуто ли описание * @property {Signal} descriptionExpandable - Можно ли развернуть описание - * @property {Signal} projectRated - Оценен ли проект + * @property {Signal} projectRated - Оценен ли проект (временно) + * @property {Signal} projectConfirmed - Подтверждена ли оценка окончательно + * @property {Signal} confirmLoading - Состояние загрузки при подтверждении */ @Component({ selector: "app-rating-card", @@ -81,9 +85,10 @@ import { TagComponent } from "@ui/components/tag/tag.component"; ControlErrorPipe, RouterLink, TagComponent, + ModalComponent, ], }) -export class RatingCardComponent implements AfterViewInit, OnDestroy { +export class RatingCardComponent implements OnInit, AfterViewInit, OnDestroy { constructor( public industryService: IndustryService, private projectRatingService: ProjectRatingService, @@ -94,7 +99,6 @@ export class RatingCardComponent implements AfterViewInit, OnDestroy { @Input({ required: true }) set project(proj: ProjectRate | null) { if (!proj) return; this._project.set(proj); - this.projectRated.set(proj.isScored); } get project(): ProjectRate | null { @@ -110,6 +114,7 @@ export class RatingCardComponent implements AfterViewInit, OnDestroy { form = new FormControl(); submitLoading = signal(false); + confirmLoading = signal(false); readFullDescription = signal(false); @@ -117,13 +122,33 @@ export class RatingCardComponent implements AfterViewInit, OnDestroy { projectRated = signal(false); + projectConfirmed = signal(false); + + showConfirmRateModal = signal(false); + + isProjectCriterias = signal(0); + desktopMode$: Observable = this.breakpointObserver .observe("(min-width: 920px)") .pipe(map(result => result.matches)); subscriptions$ = signal([]); + ngOnInit(): void { + if (this.project) { + const isScored = this.project?.scored || false; + this.projectConfirmed.set(isScored); + this.projectRated.set(isScored); + } + } + ngAfterViewInit(): void { + if (this.project) { + this.isProjectCriterias.set( + this.project?.criterias.filter(criteria => !(criteria.type === "str")).length + ); + } + const descElement = this.descEl?.nativeElement; this.descriptionExpandable.set(descElement?.clientHeight < descElement?.scrollHeight); this.cdRef.detectChanges(); @@ -146,26 +171,51 @@ export class RatingCardComponent implements AfterViewInit, OnDestroy { this.readFullDescription.set(!isExpanded); } - submitRating(): void { + confirmRateProject(): void { this.form.markAsTouched(); - if (this.form.invalid) return; const fv = this.form.getRawValue(); - const project = this.project as ProjectRate; - - const sumbittedVal = this.projectRatingService.formValuesToDTO(project.criterias, fv); + const submittedVal = this.projectRatingService.formValuesToDTO(project.criterias, fv); this.submitLoading.set(true); this.projectRatingService - .rate(project.id, sumbittedVal) + .rate(project.id, submittedVal) .pipe(finalize(() => this.submitLoading.set(false))) - .subscribe(() => this.projectRated.set(true)); + .subscribe({ + next: () => { + if (this.showConfirmRateModal()) { + this.projectConfirmed.set(true); + this.showConfirmRateModal.set(false); + } else { + this.projectRated.set(true); + this.showConfirmRateModal.set(true); + } + }, + }); } redoRating(): void { - this.projectRated.set(false); + if (!this.projectConfirmed()) { + this.projectRated.set(false); + } + } + + get canEdit(): boolean { + return !this.projectConfirmed(); + } + + get showRatingForm(): boolean { + return !this.projectRated() && this.canEdit; + } + + get showRatedWithEdit(): boolean { + return this.projectRated() && !this.projectConfirmed(); + } + + get showConfirmedState(): boolean { + return this.projectConfirmed(); } } diff --git a/projects/social_platform/src/app/office/projects/dashboard/dashboard.component.html b/projects/social_platform/src/app/office/projects/dashboard/dashboard.component.html new file mode 100644 index 000000000..0770d3e08 --- /dev/null +++ b/projects/social_platform/src/app/office/projects/dashboard/dashboard.component.html @@ -0,0 +1,23 @@ + + +
+ + + +
diff --git a/projects/social_platform/src/app/office/projects/dashboard/dashboard.component.scss b/projects/social_platform/src/app/office/projects/dashboard/dashboard.component.scss new file mode 100644 index 000000000..612469fb7 --- /dev/null +++ b/projects/social_platform/src/app/office/projects/dashboard/dashboard.component.scss @@ -0,0 +1,7 @@ +@use "styles/responsive"; + +.dashboard { + display: flex; + flex-direction: column; + gap: 13px; +} diff --git a/projects/social_platform/src/app/office/projects/dashboard/dashboard.component.ts b/projects/social_platform/src/app/office/projects/dashboard/dashboard.component.ts new file mode 100644 index 000000000..3c9ade462 --- /dev/null +++ b/projects/social_platform/src/app/office/projects/dashboard/dashboard.component.ts @@ -0,0 +1,42 @@ +/** @format */ + +import { CommonModule } from "@angular/common"; +import { Component, inject, OnDestroy, OnInit } from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; +import { Project } from "@office/models/project.model"; +import { Subscription } from "rxjs"; +import { DashboardItemComponent } from "./shared/dashboardItem/dashboardItem.component"; + +@Component({ + selector: "app-dashboard", + templateUrl: "./dashboard.component.html", + styleUrl: "./dashboard.component.scss", + imports: [CommonModule, DashboardItemComponent], + standalone: true, +}) +export class DashboardProjectsComponent implements OnInit, OnDestroy { + private readonly route = inject(ActivatedRoute); + private readonly router = inject(Router); + + allProjects: Project[] = []; + myProjects: Project[] = []; + mySubs: Project[] = []; + profileProjSubsIds?: number[]; + + subscriptions$: Subscription[] = []; + + ngOnInit(): void { + this.route.data.subscribe({ + next: ({ data: { all, my, subs } }) => { + this.allProjects = all.results.slice(0, 4); + this.myProjects = my.results.filter((project: Project) => !project.draft).slice(0, 4); + this.mySubs = subs.results.slice(0, 4); + this.profileProjSubsIds = subs.results.map((project: Project) => project.id); + }, + }); + } + + ngOnDestroy(): void { + this.subscriptions$.forEach($ => $.unsubscribe()); + } +} diff --git a/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.html b/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.html new file mode 100644 index 000000000..301e9a8ef --- /dev/null +++ b/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.html @@ -0,0 +1,31 @@ + + +
+
+

{{ title }}

+ +
+ + @if (arrayItems.length) { +
    + @for (project of arrayItems; track project.id) { + + + + } +
+ } @else { +
+ +
+ } + + + показать раздел + + +
diff --git a/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.scss b/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.scss new file mode 100644 index 000000000..c431123ad --- /dev/null +++ b/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.scss @@ -0,0 +1,48 @@ +@use "styles/responsive"; + +.dashboard { + &__top { + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 0.5px solid var(--accent); + padding-bottom: 10px; + margin-bottom: 10px; + + i, p { + color: var(--accent); + } + } + + &__list { + display: grid; + flex-grow: 1; + grid-template-columns: 1fr; + column-gap: 20px; + margin-top: 50px; + align-items: flex-start; + + @include responsive.apply-desktop { + grid-template-columns: repeat(4, 2fr); + } + } + + &__link { + display: flex; + align-items: center; + justify-content: center; + margin-top: 10px; + gap: 10px; + color: var(--grey-for-text) !important; + opacity: 1; + + &--icon { + margin-bottom: 2.5px; + color: var(--grey-for-text) !important; + } + + &:hover { + opacity: 0.5; + } + } +} diff --git a/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.ts b/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.ts new file mode 100644 index 000000000..b287a70cf --- /dev/null +++ b/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.ts @@ -0,0 +1,47 @@ +/** @format */ + +import { CommonModule } from "@angular/common"; +import { Component, inject, Input, OnInit } from "@angular/core"; +import { Project } from "@office/models/project.model"; +import { ProjectCardComponent } from "@office/shared/project-card/project-card.component"; +import { IconComponent } from "@uilib"; +import { RouterLink } from "@angular/router"; +import { ProjectsService } from "@office/projects/services/projects.service"; + +@Component({ + selector: "app-dashboard-item", + templateUrl: "./dashboardItem.component.html", + styleUrl: "./dashboardItem.component.scss", + standalone: true, + imports: [ProjectCardComponent, CommonModule, IconComponent, RouterLink], +}) +export class DashboardItemComponent implements OnInit { + @Input() title!: string; + @Input() arrayItems!: Project[]; + @Input() iconName!: string; + @Input() sectionName!: string; + @Input() profileProjSubsIds?: number[]; + + appereance: "base" | "subs" | "my" = "base"; + + private readonly projectsService = inject(ProjectsService); + + ngOnInit(): void { + switch (this.iconName) { + case "favourities": + this.appereance = "subs"; + break; + + case "main": + this.appereance = "my"; + break; + + default: + break; + } + } + + addProject(): void { + this.projectsService.addProject(); + } +} diff --git a/projects/social_platform/src/app/office/projects/list/list.component.ts b/projects/social_platform/src/app/office/projects/list/list.component.ts index f54b50077..b5b26b827 100644 --- a/projects/social_platform/src/app/office/projects/list/list.component.ts +++ b/projects/social_platform/src/app/office/projects/list/list.component.ts @@ -37,6 +37,7 @@ import { IconComponent } from "@ui/components"; import { SubscriptionService } from "@office/services/subscription.service"; import { Invite } from "@office/models/invite.model"; import { InviteService } from "@office/services/invite.service"; +import { inviteToProjectMapper } from "@utils/inviteToProjectMapper"; /** * КОМПОНЕНТ СПИСКА ПРОЕКТОВ @@ -180,16 +181,7 @@ export class ProjectsListComponent implements OnInit, AfterViewInit, OnDestroy { this.projectsCount = projects.count; if (this.isInvites) { - this.projects = (projects ?? []).map((invite: Invite) => ({ - inviteId: invite.id, - id: invite.project.id, - imageAddress: invite.project.imageAddress, - vacancies: invite.project.vacancies, - collaborators: invite.project.collaborators, - name: invite.project.name, - shortDescription: invite.user.firstName + " " + invite.user.lastName, - industry: invite.project.industry, - })); + this.projects = inviteToProjectMapper(projects ?? []); } else { this.projects = projects.results ?? []; } diff --git a/projects/social_platform/src/app/office/projects/projects.component.html b/projects/social_platform/src/app/office/projects/projects.component.html index 662514329..6b1a35a09 100644 --- a/projects/social_platform/src/app/office/projects/projects.component.html +++ b/projects/social_platform/src/app/office/projects/projects.component.html @@ -2,46 +2,48 @@
- +
-
+
+ @if(!isDashboard) { + }
@@ -50,14 +52,34 @@ создать проект
- @if (isMy) { + @if (isDashboard) { +
+
+

мои приглашения

+ +
+ + @if (myInvites.length) { +
    + @for (invite of myInvites; track invite.id) { + + } +
+ } @else { +
+

пока нет приглашений

+
+ } +
+ } @if (isMy || isDashboard) { r["data"])).subscribe({ + next: invites => { + this.myInvites = inviteToProjectMapper(invites.slice(0, 1)); + }, + }); + const searchFormSearch$ = this.searchForm.get("search")?.valueChanges.subscribe(search => { this.router .navigate([], { @@ -75,19 +83,13 @@ export class ProjectsComponent implements OnInit, OnDestroy { searchFormSearch$ && this.subscriptions$.push(searchFormSearch$); - const routeData$ = this.route.data - .pipe(map(r => r["data"])) - .subscribe((count: ProjectCount) => { - this.projectService.projectsCount.next(count); - }); - - routeData$ && this.subscriptions$.push(routeData$); - const routeUrl$ = this.router.events.subscribe(event => { if (event instanceof NavigationEnd) { this.isMy = location.href.includes("/my"); this.isAll = location.href.includes("/all"); this.isSubs = location.href.includes("/subscriptions"); + this.isInvites = location.href.includes("/invites"); + this.isDashboard = location.href.includes("/dashboard"); } }); routeUrl$ && this.subscriptions$.push(routeUrl$); @@ -98,12 +100,16 @@ export class ProjectsComponent implements OnInit, OnDestroy { } searchForm: FormGroup; + + myInvites: Project[] = []; + subscriptions$: Subscription[] = []; isMy = location.href.includes("/my"); isAll = location.href.includes("/all"); isSubs = location.href.includes("/subscriptions"); isInvites = location.href.includes("/invites"); + isDashboard = location.href.includes("/dashboard"); isFilterOpen = false; @@ -150,15 +156,6 @@ export class ProjectsComponent implements OnInit, OnDestroy { } addProject(): void { - this.projectService.create().subscribe(project => { - this.projectService.projectsCount.next({ - ...this.projectService.projectsCount.getValue(), - my: this.projectService.projectsCount.getValue().my + 1, - }); - - this.router - .navigateByUrl(`/office/projects/${project.id}/edit?editingStep=main`) - .then(() => console.debug("Route change from ProjectsComponent")); - }); + this.projectsService.addProject(); } } diff --git a/projects/social_platform/src/app/office/projects/projects.resolver.ts b/projects/social_platform/src/app/office/projects/projects.resolver.ts index 3ccf6ad57..c32dadf54 100644 --- a/projects/social_platform/src/app/office/projects/projects.resolver.ts +++ b/projects/social_platform/src/app/office/projects/projects.resolver.ts @@ -1,12 +1,14 @@ /** @format */ import { inject } from "@angular/core"; -import { forkJoin, map, switchMap } from "rxjs"; -import { ProjectCount } from "@models/project.model"; +import { forkJoin, switchMap } from "rxjs"; +import { Project } from "@models/project.model"; import { ProjectService } from "@services/project.service"; import { AuthService } from "@auth/services"; import { ResolveFn } from "@angular/router"; import { SubscriptionService } from "@office/services/subscription.service"; +import { HttpParams } from "@angular/common/http"; +import { ApiPagination } from "@office/models/api-pagination.model"; /** * Resolver для загрузки данных о количестве проектов @@ -28,17 +30,25 @@ import { SubscriptionService } from "@office/services/subscription.service"; * Используется перед загрузкой ProjectsComponent для предварительной * загрузки необходимых данных. */ -export const ProjectsResolver: ResolveFn = () => { + +export interface DashboardProjectsData { + all: ApiPagination; + my: ApiPagination; + subs: ApiPagination; +} + +export const ProjectsResolver: ResolveFn = () => { const projectService = inject(ProjectService); const authService = inject(AuthService); const subscriptionService = inject(SubscriptionService); return authService.profile.pipe( - switchMap(p => { - return forkJoin([ - projectService.getCount(), // Получение количества проектов - subscriptionService.getSubscriptions(p.id).pipe(map(resp => resp.count)), // Получение количества подписок - ]).pipe(map(([countData, subsCount]) => ({ ...countData, subs: subsCount }))); - }) + switchMap(user => + forkJoin({ + all: projectService.getAll(new HttpParams({ fromObject: { limit: 16 } })), + my: projectService.getMy(new HttpParams({ fromObject: { limit: 16 } })), + subs: subscriptionService.getSubscriptions(user.id), + }) + ) ); }; diff --git a/projects/social_platform/src/app/office/projects/projects.routes.ts b/projects/social_platform/src/app/office/projects/projects.routes.ts index 7e555d7fd..2b9196cfe 100644 --- a/projects/social_platform/src/app/office/projects/projects.routes.ts +++ b/projects/social_platform/src/app/office/projects/projects.routes.ts @@ -11,6 +11,7 @@ import { ProjectEditResolver } from "./edit/edit.resolver"; import { ProjectsSubscriptionsResolver } from "./list/subscriptions.resolver"; import { ProjectEditRequiredGuard } from "./edit/guards/projects-edit.guard"; import { ProjectsInvitesResolver } from "./list/invites.resolver"; +import { DashboardProjectsComponent } from "./dashboard/dashboard.component"; /** * Конфигурация маршрутов для модуля проектов @@ -40,13 +41,20 @@ export const PROJECTS_ROUTES: Routes = [ path: "", component: ProjectsComponent, resolve: { - data: ProjectsResolver, + data: ProjectsInvitesResolver, }, children: [ { path: "", pathMatch: "full", - redirectTo: "my", + redirectTo: "dashboard", + }, + { + path: "dashboard", + component: DashboardProjectsComponent, + resolve: { + data: ProjectsResolver, + }, }, { path: "my", diff --git a/projects/social_platform/src/app/office/projects/services/projects.service.ts b/projects/social_platform/src/app/office/projects/services/projects.service.ts new file mode 100644 index 000000000..0146fd4b7 --- /dev/null +++ b/projects/social_platform/src/app/office/projects/services/projects.service.ts @@ -0,0 +1,26 @@ +/** @format */ + +import { inject, Injectable } from "@angular/core"; +import { Router } from "@angular/router"; +import { ProjectService } from "@office/services/project.service"; + +@Injectable({ + providedIn: "root", +}) +export class ProjectsService { + private readonly projectService = inject(ProjectService); + private readonly router = inject(Router); + + addProject(): void { + this.projectService.create().subscribe(project => { + this.projectService.projectsCount.next({ + ...this.projectService.projectsCount.getValue(), + my: this.projectService.projectsCount.getValue().my + 1, + }); + + this.router + .navigateByUrl(`/office/projects/${project.id}/edit?editingStep=main`) + .then(() => console.debug("Route change from ProjectsComponent")); + }); + } +} diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.html b/projects/social_platform/src/app/office/shared/project-card/project-card.component.html index 62dcddbcd..3afcf6739 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.html +++ b/projects/social_platform/src/app/office/shared/project-card/project-card.component.html @@ -1,144 +1,163 @@ -
+
- @if (type === 'project') { + @if (shouldShowProjectInfo()) {
-

12

-

12

- } - + } @if (shouldShowSubscriptionBadge()) {
- @if (haveBadge) { @if (isSubscribed) { - } @else { - - } }
+ } - +
-

{{ project.name }}

- @if (industryService.industries | async; as industries) { -

- @if (industryService.getIndustry(industries, project.industry); as industry) { - - {{ industry ? industry.name : "Error" }} - - } -

+ @if (appereance === 'empty') { + + } @else { + }
- @if (type === 'invite') { -
-

вас приглашает

-

{{ project.shortDescription }}

-
- } @else { -
-

{{ project.shortDescription }}

-
+ @if (appereance !== 'empty') { + } -
- @if (type === 'project') { - - проект - +
- -
-

Вы действительно хотите отписаться от проекта?

- -
- - Отписаться - - - Отменить - -
-
-
+ +
+
+ + +

{{ project?.name }}

+ Стрелка +

создай первый проект

+
+ + +

{{ project?.name || "Без названия" }}

+ @if (industryService.industries | async; as industries) { +

+ @if (industryService.getIndustry(industries, project?.industry!); as industry) { + {{ industry?.name || "Отрасль не указана" }} + } +

+ } +
+ + +
+ @if (type === 'invite') { +

вас приглашает

+

{{ project?.shortDescription }}

+ } @else { +

{{ project?.shortDescription }}

+ } +
+
+ + +
+ @if (type === 'project') { + + проект + + } @else { +
+ + принять + + + отклонить + +
+ } +
+
- -
-

- Приглашение на текущий проект было удалено -

-

- Проверьте наличие вас в списке участников проекта или обратитесь к создателю проекта, - чтобы вас заново пригласили! -

+ + +
+

Вы действительно хотите отписаться от проекта?

+ +
+ + Отписаться + - Хорошо + Отменить
- -
-
+
+ + + +
+

Приглашение на текущий проект было удалено

+

+ Проверьте наличие вас в списке участников проекта или обратитесь к создателю проекта, чтобы + вас заново пригласили! +

+ + Хорошо + +
+
+ diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss b/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss index dd6f17515..73a3b10a8 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss +++ b/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss @@ -8,6 +8,7 @@ display: flex; flex-direction: column; height: 170px; + width: 156px; padding: 15px 0 12px; background-color: var(--light-white); border: 0.5px solid var(--medium-for-outline); @@ -123,6 +124,39 @@ color: var(--white); } } + + &__empty { + border: 0.5px dashed var(--accent); + padding: 35px 0px 12px; + background: transparent; + margin-top: 50px; + position: relative; + opacity: 1; + transition: opacity 0.2s ease; + cursor: pointer; + + &:hover { + opacity: 0.7; + } + + &--image { + position: absolute; + right: 20%; + top: 15%; + } + + &--name { + display: flex; + justify-content: center; + width: 90%; + color: var(--accent); + margin-left: 8px; + } + + &--icon { + color: var(--accent); + } + } } .message-modal { diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.ts b/projects/social_platform/src/app/office/shared/project-card/project-card.component.ts index 100c2b3d0..7f5aa2869 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.ts +++ b/projects/social_platform/src/app/office/shared/project-card/project-card.component.ts @@ -9,31 +9,10 @@ import { AsyncPipe, CommonModule } from "@angular/common"; import { ModalComponent } from "@ui/components/modal/modal.component"; import { SubscriptionService } from "@office/services/subscription.service"; import { InviteService } from "@office/services/invite.service"; -import { Router } from "@angular/router"; import { ClickOutsideModule } from "ng-click-outside"; /** * Компонент карточки проекта - * - * Функциональность: - * - Отображает основную информацию о проекте (название, описание, участники) - * - Показывает аватары участников проекта - * - Отображает информацию об отрасли проекта через IndustryService - * - Предоставляет кнопку удаления проекта (корзина) при наличии прав - * - Поддерживает индикацию подписки на проект - * - Проверяет права доступа на основе ID профиля пользователя - * - * Входные параметры: - * @Input project - объект проекта (обязательный) - * @Input canDelete - флаг возможности удаления проекта (по умолчанию false) - * @Input isSubscribed - флаг подписки на проект (по умолчанию false) - * @Input profileId - ID профиля текущего пользователя - * - * Выходные события: - * @Output remove - событие удаления проекта, передает ID проекта - * - * Внутренние свойства: - * - industryService - сервис для работы с отраслями (публичный для использования в шаблоне) */ @Component({ selector: "app-project-card", @@ -53,83 +32,154 @@ import { ClickOutsideModule } from "ng-click-outside"; export class ProjectCardComponent implements OnInit { private readonly inviteService = inject(InviteService); private readonly subscriptionService = inject(SubscriptionService); - private readonly router = inject(Router); public readonly industryService = inject(IndustryService); - @Input({ required: true }) project!: Project; + @Input() project?: Project; @Input() type: "invite" | "project" = "project"; + @Input() appereance: "my" | "subs" | "base" | "empty" = "base"; @Input() canDelete?: boolean | null = false; @Input() isSubscribed?: boolean | null = false; @Input() profileId?: number; @Output() onAcceptingInvite = new EventEmitter(); @Output() onRejectingInvite = new EventEmitter(); + @Output() onCreate = new EventEmitter(); - ngOnInit(): void {} + // Состояние компонента + isUnsubscribeModalOpen = false; + inviteErrorModal = false; + haveBadge = this.calculateHaveBadge(); + ngOnInit(): void { + this.validateInputs(); + } + + /** + * Определяет, нужно ли показывать информацию о проекте + */ + shouldShowProjectInfo(): boolean { + return this.type === "project" && this.appereance !== "subs" && this.appereance !== "empty"; + } + + /** + * Определяет, нужно ли показывать бейдж подписки + */ + shouldShowSubscriptionBadge(): boolean { + return ( + this.appereance !== "empty" && + this.haveBadge && + this.appereance === "base" && + this.type !== "invite" + ); + } + + /** + * Возвращает URL для аватара + */ + getAvatarUrl(): string { + return this.project?.imageAddress || "/assets/images/projects/shared/add-project.svg"; + } + + /** + * Переключение подписки (универсальный метод) + */ + toggleSubscription(event: Event): void { + if (this.isSubscribed) { + this.onSubscribe(event, this.profileId!); + } else { + this.onSubscribe(event, this.profileId!); + } + } + + /** + * Обработка отклонения приглашения + */ onRejectInvite(event: Event, inviteId: number): void { - event.stopPropagation(); - event.preventDefault(); + if (!this.project || !inviteId) { + console.warn("Cannot reject invite: missing project or inviteId"); + return; + } + + this.stopEventPropagation(event); this.inviteService.rejectInvite(inviteId).subscribe({ next: () => { - this.onRejectingInvite.emit(inviteId || this.project.inviteId); + this.onRejectingInvite.emit(inviteId || this.project!.inviteId); }, - error: () => { + error: error => { + console.error("Error rejecting invite:", error); this.inviteErrorModal = true; }, }); } + /** + * Обработка принятия приглашения + */ onAcceptInvite(event: Event, inviteId: number): void { - event.stopPropagation(); - event.preventDefault(); + if (!this.project || !inviteId) { + console.warn("Cannot accept invite: missing project or inviteId"); + return; + } + + this.stopEventPropagation(event); this.inviteService.acceptInvite(inviteId).subscribe({ next: () => { - this.onAcceptingInvite.emit(inviteId || this.project.inviteId); + this.onAcceptingInvite.emit(inviteId || this.project!.inviteId); }, - error: () => { + error: error => { + console.error("Error accepting invite:", error); this.inviteErrorModal = true; }, }); } - isUnsubscribeModalOpen = false; // Флаг модального окна отписки - inviteErrorModal = false; // Флаг модального окна для ошибки приглашения - haveBadge = - location.href.includes("/subscriptions") || - location.href.includes("/all") || - location.href.includes("/projects"); - /** * Подписка на проект или открытие модального окна отписки - * @param projectId - ID проекта */ onSubscribe(event: Event, projectId: number): void { - event.stopPropagation(); - event.preventDefault(); + if (!projectId) { + console.warn("Cannot subscribe: missing projectId"); + return; + } + + this.stopEventPropagation(event); if (this.isSubscribed) { this.isUnsubscribeModalOpen = true; return; } - this.subscriptionService.addSubscription(projectId).subscribe(() => { - this.isSubscribed = true; + + this.subscriptionService.addSubscription(projectId).subscribe({ + next: () => { + this.isSubscribed = true; + }, + error: error => { + console.error("Error subscribing to project:", error); + }, }); } /** * Отписка от проекта - * @param projectId - ID проекта */ onUnsubscribe(event: Event, projectId: number): void { - event.stopPropagation(); - event.preventDefault(); + if (!projectId) { + console.warn("Cannot unsubscribe: missing projectId"); + return; + } - this.subscriptionService.deleteSubscription(projectId).subscribe(() => { - this.isSubscribed = false; - this.isUnsubscribeModalOpen = false; + this.stopEventPropagation(event); + + this.subscriptionService.deleteSubscription(projectId).subscribe({ + next: () => { + this.isSubscribed = false; + this.isUnsubscribeModalOpen = false; + }, + error: error => { + console.error("Error unsubscribing from project:", error); + }, }); } @@ -139,4 +189,40 @@ export class ProjectCardComponent implements OnInit { onCloseUnsubscribeModal(): void { this.isUnsubscribeModalOpen = false; } + + /** + * Обработка создания нового проекта + */ + onCreateProject(event: Event): void { + this.stopEventPropagation(event); + this.onCreate.emit(); + } + + /** + * Остановка всплытия события + */ + private stopEventPropagation(event: Event): void { + event.stopPropagation(); + event.preventDefault(); + } + + /** + * Вычисление флага haveBadge + */ + private calculateHaveBadge(): boolean { + return ( + location.href.includes("/subscriptions") || + location.href.includes("/all") || + location.href.includes("/projects") + ); + } + + /** + * Валидация входных параметров + */ + private validateInputs(): void { + if (this.appereance !== "empty" && !this.project) { + console.warn('ProjectCardComponent: project is required when appearance is not "empty"'); + } + } } diff --git a/projects/social_platform/src/app/office/shared/project-rating/components/range-criterion-input/range-criterion-input.component.html b/projects/social_platform/src/app/office/shared/project-rating/components/range-criterion-input/range-criterion-input.component.html index ee71c8ee9..8473c2f3a 100644 --- a/projects/social_platform/src/app/office/shared/project-rating/components/range-criterion-input/range-criterion-input.component.html +++ b/projects/social_platform/src/app/office/shared/project-rating/components/range-criterion-input/range-criterion-input.component.html @@ -12,7 +12,7 @@ (blur)="onBlur()" (paste)="onPaste($event)" (focus)="moveCursorToEnd($event)" - class="field__input" + class="field__input text-body-10" [class.field__input--error]="error" />  / diff --git a/projects/social_platform/src/app/office/shared/project-rating/components/range-criterion-input/range-criterion-input.component.scss b/projects/social_platform/src/app/office/shared/project-rating/components/range-criterion-input/range-criterion-input.component.scss index 6414880a0..f4204ad1f 100644 --- a/projects/social_platform/src/app/office/shared/project-rating/components/range-criterion-input/range-criterion-input.component.scss +++ b/projects/social_platform/src/app/office/shared/project-rating/components/range-criterion-input/range-criterion-input.component.scss @@ -38,8 +38,6 @@ border-color: var(--accent); box-shadow: 0 0 6px rgb(109 40 255 / 30%); } - - @include typography.body-14; } } diff --git a/projects/social_platform/src/app/office/shared/project-rating/project-rating.component.scss b/projects/social_platform/src/app/office/shared/project-rating/project-rating.component.scss index 5a6ac923a..2e83a0f54 100644 --- a/projects/social_platform/src/app/office/shared/project-rating/project-rating.component.scss +++ b/projects/social_platform/src/app/office/shared/project-rating/project-rating.component.scss @@ -20,6 +20,7 @@ label { margin-top: 5px; + color: var(--grey-for-text); } } @@ -33,6 +34,9 @@ app-textarea { ::ng-deep .field__input { color: var(--dark-grey); + background-color: transparent; + border-color: var(--grey-button); + min-height: 66px !important; @include typography.body-10; } diff --git a/projects/social_platform/src/app/utils/inviteToProjectMapper.ts b/projects/social_platform/src/app/utils/inviteToProjectMapper.ts new file mode 100644 index 000000000..39cb4fc41 --- /dev/null +++ b/projects/social_platform/src/app/utils/inviteToProjectMapper.ts @@ -0,0 +1,17 @@ +/** @format */ + +import { Invite } from "@office/models/invite.model"; +import { Project } from "@office/models/project.model"; + +export const inviteToProjectMapper = (invites: Invite[] = []): any[] => { + return (invites ?? []).map((invite: Invite) => ({ + inviteId: invite.id, + id: invite.project.id, + imageAddress: invite.project.imageAddress, + vacancies: invite.project.vacancies, + collaborators: invite.project.collaborators, + name: invite.project.name, + shortDescription: invite.user.firstName + " " + invite.user.lastName, + industry: invite.project.industry, + })); +}; diff --git a/projects/social_platform/src/assets/icons/svg/feed.svg b/projects/social_platform/src/assets/icons/svg/feed.svg index 11054ce46..52d84d104 100644 --- a/projects/social_platform/src/assets/icons/svg/feed.svg +++ b/projects/social_platform/src/assets/icons/svg/feed.svg @@ -1,3 +1,3 @@ - - + + diff --git a/projects/social_platform/src/assets/icons/svg/message.svg b/projects/social_platform/src/assets/icons/svg/message.svg index 99e88e075..a69254b9d 100644 --- a/projects/social_platform/src/assets/icons/svg/message.svg +++ b/projects/social_platform/src/assets/icons/svg/message.svg @@ -1,5 +1,3 @@ - - - - + + diff --git a/projects/social_platform/src/assets/icons/svg/people-bold.svg b/projects/social_platform/src/assets/icons/svg/people-bold.svg index ecc4d553b..af2ee0330 100644 --- a/projects/social_platform/src/assets/icons/svg/people-bold.svg +++ b/projects/social_platform/src/assets/icons/svg/people-bold.svg @@ -1,8 +1,3 @@ - - - - - - - + + diff --git a/projects/social_platform/src/assets/icons/svg/projects-filled.svg b/projects/social_platform/src/assets/icons/svg/projects-filled.svg deleted file mode 100644 index a1bcb8ca0..000000000 --- a/projects/social_platform/src/assets/icons/svg/projects-filled.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/projects/social_platform/src/assets/icons/svg/projects.svg b/projects/social_platform/src/assets/icons/svg/projects.svg index ba263dff9..dc01a0be2 100644 --- a/projects/social_platform/src/assets/icons/svg/projects.svg +++ b/projects/social_platform/src/assets/icons/svg/projects.svg @@ -1,6 +1,3 @@ - - - - - + + diff --git a/projects/social_platform/src/assets/icons/svg/trajectories.svg b/projects/social_platform/src/assets/icons/svg/trajectories.svg new file mode 100644 index 000000000..7391d278a --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/trajectories.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg index 5c733b731..4fdf2dd34 100644 --- a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg +++ b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/projects/social_platform/src/assets/images/projects/shared/add-project.svg b/projects/social_platform/src/assets/images/projects/shared/add-project.svg new file mode 100644 index 000000000..07286d292 --- /dev/null +++ b/projects/social_platform/src/assets/images/projects/shared/add-project.svg @@ -0,0 +1,4 @@ + + + + diff --git a/projects/social_platform/src/assets/images/projects/shared/arrow.svg b/projects/social_platform/src/assets/images/projects/shared/arrow.svg new file mode 100644 index 000000000..80abe8811 --- /dev/null +++ b/projects/social_platform/src/assets/images/projects/shared/arrow.svg @@ -0,0 +1,3 @@ + + + From 545d6b6e4b807d433639e81a8c1d9639d08c9087 Mon Sep 17 00:00:00 2001 From: Awakich Date: Tue, 16 Sep 2025 19:16:24 +0300 Subject: [PATCH 066/126] change styles for rate-project, dashboard for projects pages, project-card, rating-card components --- .../program/detail/main/main.component.scss | 2 +- .../rate-projects/rate-projects.component.scss | 6 +++--- .../rating-card/rating-card.component.scss | 6 +++--- .../dashboardItem/dashboardItem.component.scss | 9 +++++---- .../app/office/projects/projects.component.scss | 13 +++++++------ .../project-card/project-card.component.scss | 16 ++++++++-------- .../project-rating/project-rating.component.scss | 2 +- 7 files changed, 28 insertions(+), 26 deletions(-) diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.scss b/projects/social_platform/src/app/office/program/detail/main/main.component.scss index 21007a1cb..891ed89e2 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.scss +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.scss @@ -20,10 +20,10 @@ &__section { padding: 24px; + margin-bottom: 14px; background-color: var(--light-white); border: 0.5px solid var(--medium-grey-for-outline); border-radius: var(--rounded-lg); - margin-bottom: 14px; } &__info { diff --git a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.scss b/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.scss index 636445836..a5b4b4d47 100644 --- a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.scss +++ b/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.scss @@ -4,8 +4,8 @@ .page { display: grid; grid-template-columns: 8fr 2fr; - padding-bottom: 100px; gap: 20px; + padding-bottom: 100px; &__search { flex-grow: 1; @@ -63,16 +63,16 @@ } &__controls { - margin-top: 18px; display: flex; flex-direction: column; gap: 14px; + margin-top: 18px; } &__tags { display: flex; - align-items: center; gap: 12px; + align-items: center; p { color: var(--grey-for-text); diff --git a/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.scss b/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.scss index 22b985814..e66f90877 100644 --- a/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.scss +++ b/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.scss @@ -51,11 +51,11 @@ &__rated { display: flex; - align-items: center; gap: 4px; + align-items: center; &--icon { - ::ng-deep { + ::ng-deep { button { width: 50px; padding: 8px !important; @@ -171,8 +171,8 @@ max-height: calc(100vh - 40px); i { + padding: 36px 0; color: var(--green); - padding: 36px 0px; } &__top { diff --git a/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.scss b/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.scss index c431123ad..b2b495b4e 100644 --- a/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.scss +++ b/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.scss @@ -5,11 +5,12 @@ display: flex; align-items: center; justify-content: space-between; - border-bottom: 0.5px solid var(--accent); padding-bottom: 10px; margin-bottom: 10px; + border-bottom: 0.5px solid var(--accent); - i, p { + i, + p { color: var(--accent); } } @@ -19,8 +20,8 @@ flex-grow: 1; grid-template-columns: 1fr; column-gap: 20px; - margin-top: 50px; align-items: flex-start; + margin-top: 50px; @include responsive.apply-desktop { grid-template-columns: repeat(4, 2fr); @@ -29,10 +30,10 @@ &__link { display: flex; + gap: 10px; align-items: center; justify-content: center; margin-top: 10px; - gap: 10px; color: var(--grey-for-text) !important; opacity: 1; diff --git a/projects/social_platform/src/app/office/projects/projects.component.scss b/projects/social_platform/src/app/office/projects/projects.component.scss index a157cd428..fc8a325fd 100644 --- a/projects/social_platform/src/app/office/projects/projects.component.scss +++ b/projects/social_platform/src/app/office/projects/projects.component.scss @@ -40,11 +40,12 @@ display: flex; align-items: center; justify-content: space-between; - border-bottom: 0.5px solid var(--accent); padding-bottom: 8px; margin-bottom: 13px; + border-bottom: 0.5px solid var(--accent); - i, p { + i, + p { color: var(--accent); } } @@ -55,13 +56,13 @@ } &--no-items { - text-align: center; - border-radius: var(--rounded-lg); - border: 0.5px dashed var(--grey-for-text); - padding: 16px 5px; display: flex; justify-content: center; + padding: 16px 5px; margin-bottom: 8px; + text-align: center; + border: 0.5px dashed var(--grey-for-text); + border-radius: var(--rounded-lg); p { width: 90%; diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss b/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss index 73a3b10a8..d9c90768e 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss +++ b/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss @@ -7,8 +7,8 @@ position: relative; display: flex; flex-direction: column; - height: 170px; width: 156px; + height: 170px; padding: 15px 0 12px; background-color: var(--light-white); border: 0.5px solid var(--medium-for-outline); @@ -126,14 +126,14 @@ } &__empty { - border: 0.5px dashed var(--accent); - padding: 35px 0px 12px; - background: transparent; - margin-top: 50px; position: relative; + padding: 35px 0 12px; + margin-top: 50px; + cursor: pointer; + background: transparent; + border: 0.5px dashed var(--accent); opacity: 1; transition: opacity 0.2s ease; - cursor: pointer; &:hover { opacity: 0.7; @@ -141,16 +141,16 @@ &--image { position: absolute; - right: 20%; top: 15%; + right: 20%; } &--name { display: flex; justify-content: center; width: 90%; - color: var(--accent); margin-left: 8px; + color: var(--accent); } &--icon { diff --git a/projects/social_platform/src/app/office/shared/project-rating/project-rating.component.scss b/projects/social_platform/src/app/office/shared/project-rating/project-rating.component.scss index 2e83a0f54..7ab995d5c 100644 --- a/projects/social_platform/src/app/office/shared/project-rating/project-rating.component.scss +++ b/projects/social_platform/src/app/office/shared/project-rating/project-rating.component.scss @@ -33,10 +33,10 @@ app-textarea { ::ng-deep .field__input { + min-height: 66px !important; color: var(--dark-grey); background-color: transparent; border-color: var(--grey-button); - min-height: 66px !important; @include typography.body-10; } From d94abec781a333dd8ffa57334e0e04f0b168f74a Mon Sep 17 00:00:00 2001 From: Awakich Date: Wed, 17 Sep 2025 11:39:00 +0300 Subject: [PATCH 067/126] change redirect for office --- projects/social_platform/src/app/office/office.routes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/social_platform/src/app/office/office.routes.ts b/projects/social_platform/src/app/office/office.routes.ts index 6d68b3ce2..4fd4ebc23 100644 --- a/projects/social_platform/src/app/office/office.routes.ts +++ b/projects/social_platform/src/app/office/office.routes.ts @@ -34,7 +34,7 @@ export const OFFICE_ROUTES: Routes = [ { path: "", pathMatch: "full", - redirectTo: "feed", + redirectTo: "projects", }, { path: "feed", From 19958a3e0f39b206b59835dd7b83b984b9e6ba50 Mon Sep 17 00:00:00 2001 From: Awakich Date: Thu, 18 Sep 2025 13:39:12 +0300 Subject: [PATCH 068/126] add dashboardItem builder, service for projects logic, unify members, projects modules to one list module --- .../office/program/detail/detail.routes.ts | 13 +- .../detail/detail/detail.component.scss | 3 +- .../list-all.resolver.ts | 0 .../list.component.html} | 18 +- .../list.component.scss} | 0 .../list.component.spec.ts} | 19 +- .../program/detail/list/list.component.ts | 346 +++++++++++++++++ .../{members => list}/members.resolver.ts | 0 .../projects-filter.component.html | 0 .../projects-filter.component.scss | 0 .../projects-filter.component.spec.ts | 0 .../projects-filter.component.ts | 0 .../{projects => list}/projects.resolver.ts | 2 +- .../program/detail/main/main.component.scss | 4 + .../detail/members/members.component.html | 41 -- .../detail/members/members.component.ts | 229 ----------- .../detail/members/members.resolver.spec.ts | 23 -- .../detail/projects/projects.component.scss | 146 -------- .../projects/projects.component.spec.ts | 28 -- .../detail/projects/projects.component.ts | 354 ------------------ .../detail/projects/projects.resolver.spec.ts | 23 -- .../rate-projects/list-all.resolver.spec.ts | 2 +- .../rate-projects/rate-project.routes.ts | 2 +- .../{list => }/main/main.component.html | 0 .../{list => }/main/main.component.scss | 0 .../{list => }/main/main.component.spec.ts | 0 .../program/{list => }/main/main.component.ts | 2 +- .../{list => }/main/main.resolver.spec.ts | 0 .../program/{list => }/main/main.resolver.ts | 0 .../src/app/office/program/program.routes.ts | 4 +- .../rating-card/rating-card.component.html | 1 - .../rating-card/rating-card.component.scss | 2 +- .../dashboard/dashboard.component.html | 24 +- .../projects/dashboard/dashboard.component.ts | 20 +- .../dashboardItem.component.html | 14 +- .../dashboardItem/dashboardItem.component.ts | 3 +- .../office/projects/edit/edit.component.scss | 8 +- .../project-navigation.component.scss | 2 +- .../office/projects/list/list.component.ts | 3 - .../project-card/project-card.component.html | 26 +- .../project-card/project-card.component.scss | 12 +- .../project-card/project-card.component.ts | 20 +- .../ui/components/bar-new/bar.component.scss | 2 +- .../components/button/button.component.scss | 6 +- .../src/app/utils/dashboardItemBuilder.ts | 30 ++ .../projects/shared/empty-subscriptions.svg | 4 + 46 files changed, 516 insertions(+), 920 deletions(-) rename projects/social_platform/src/app/office/program/detail/{rate-projects => list}/list-all.resolver.ts (100%) rename projects/social_platform/src/app/office/program/detail/{projects/projects.component.html => list/list.component.html} (70%) rename projects/social_platform/src/app/office/program/detail/{members/members.component.scss => list/list.component.scss} (100%) rename projects/social_platform/src/app/office/program/detail/{members/members.component.spec.ts => list/list.component.spec.ts} (54%) create mode 100644 projects/social_platform/src/app/office/program/detail/list/list.component.ts rename projects/social_platform/src/app/office/program/detail/{members => list}/members.resolver.ts (100%) rename projects/social_platform/src/app/office/program/detail/{projects => list}/projects-filter/projects-filter.component.html (100%) rename projects/social_platform/src/app/office/program/detail/{projects => list}/projects-filter/projects-filter.component.scss (100%) rename projects/social_platform/src/app/office/program/detail/{projects => list}/projects-filter/projects-filter.component.spec.ts (100%) rename projects/social_platform/src/app/office/program/detail/{projects => list}/projects-filter/projects-filter.component.ts (100%) rename projects/social_platform/src/app/office/program/detail/{projects => list}/projects.resolver.ts (95%) delete mode 100644 projects/social_platform/src/app/office/program/detail/members/members.component.html delete mode 100644 projects/social_platform/src/app/office/program/detail/members/members.component.ts delete mode 100644 projects/social_platform/src/app/office/program/detail/members/members.resolver.spec.ts delete mode 100644 projects/social_platform/src/app/office/program/detail/projects/projects.component.scss delete mode 100644 projects/social_platform/src/app/office/program/detail/projects/projects.component.spec.ts delete mode 100644 projects/social_platform/src/app/office/program/detail/projects/projects.component.ts delete mode 100644 projects/social_platform/src/app/office/program/detail/projects/projects.resolver.spec.ts rename projects/social_platform/src/app/office/program/{list => }/main/main.component.html (100%) rename projects/social_platform/src/app/office/program/{list => }/main/main.component.scss (100%) rename projects/social_platform/src/app/office/program/{list => }/main/main.component.spec.ts (100%) rename projects/social_platform/src/app/office/program/{list => }/main/main.component.ts (97%) rename projects/social_platform/src/app/office/program/{list => }/main/main.resolver.spec.ts (100%) rename projects/social_platform/src/app/office/program/{list => }/main/main.resolver.ts (100%) create mode 100644 projects/social_platform/src/app/utils/dashboardItemBuilder.ts create mode 100644 projects/social_platform/src/assets/images/projects/shared/empty-subscriptions.svg diff --git a/projects/social_platform/src/app/office/program/detail/detail.routes.ts b/projects/social_platform/src/app/office/program/detail/detail.routes.ts index 3d29aa7d7..486c60540 100644 --- a/projects/social_platform/src/app/office/program/detail/detail.routes.ts +++ b/projects/social_platform/src/app/office/program/detail/detail.routes.ts @@ -6,11 +6,10 @@ import { ProgramDetailResolver } from "@office/program/detail/detail/detail.reso import { ProgramDetailMainComponent } from "@office/program/detail/main/main.component"; import { ProgramRegisterComponent } from "@office/program/detail/register/register.component"; import { ProgramRegisterResolver } from "@office/program/detail/register/register.resolver"; -import { ProgramProjectsComponent } from "@office/program/detail/projects/projects.component"; -import { ProgramProjectsResolver } from "@office/program/detail/projects/projects.resolver"; -import { ProgramMembersComponent } from "@office/program/detail/members/members.component"; -import { ProgramMembersResolver } from "@office/program/detail/members/members.resolver"; +import { ProgramProjectsResolver } from "@office/program/detail/list/projects.resolver"; +import { ProgramMembersResolver } from "@office/program/detail/list/members.resolver"; import { RateProjectsComponent } from "./rate-projects/rate-projects.component"; +import { ProgramListComponent } from "./list/list.component"; /** * Маршруты для детальной страницы программы @@ -39,17 +38,19 @@ export const PROGRAM_DETAIL_ROUTES: Routes = [ }, { path: "projects", - component: ProgramProjectsComponent, + component: ProgramListComponent, resolve: { data: ProgramProjectsResolver, }, + data: { listType: "projects" }, }, { path: "members", - component: ProgramMembersComponent, + component: ProgramListComponent, resolve: { data: ProgramMembersResolver, }, + data: { listType: "members" }, }, { path: "projects-rating", diff --git a/projects/social_platform/src/app/office/program/detail/detail/detail.component.scss b/projects/social_platform/src/app/office/program/detail/detail/detail.component.scss index ece475858..23fa49ddd 100644 --- a/projects/social_platform/src/app/office/program/detail/detail/detail.component.scss +++ b/projects/social_platform/src/app/office/program/detail/detail/detail.component.scss @@ -67,8 +67,9 @@ &__actions { display: grid; - grid-template-columns: repeat(5, 2fr); + grid-template-columns: 2fr 2fr 1fr 2fr 2fr; align-items: center; + gap: 20px; } &__presentation { diff --git a/projects/social_platform/src/app/office/program/detail/rate-projects/list-all.resolver.ts b/projects/social_platform/src/app/office/program/detail/list/list-all.resolver.ts similarity index 100% rename from projects/social_platform/src/app/office/program/detail/rate-projects/list-all.resolver.ts rename to projects/social_platform/src/app/office/program/detail/list/list-all.resolver.ts diff --git a/projects/social_platform/src/app/office/program/detail/projects/projects.component.html b/projects/social_platform/src/app/office/program/detail/list/list.component.html similarity index 70% rename from projects/social_platform/src/app/office/program/detail/projects/projects.component.html rename to projects/social_platform/src/app/office/program/detail/list/list.component.html index 59837006f..f969f246d 100644 --- a/projects/social_platform/src/app/office/program/detail/projects/projects.component.html +++ b/projects/social_platform/src/app/office/program/detail/list/list.component.html @@ -1,6 +1,6 @@ -
+
diff --git a/projects/social_platform/src/app/office/program/detail/members/members.component.scss b/projects/social_platform/src/app/office/program/detail/list/list.component.scss similarity index 100% rename from projects/social_platform/src/app/office/program/detail/members/members.component.scss rename to projects/social_platform/src/app/office/program/detail/list/list.component.scss diff --git a/projects/social_platform/src/app/office/program/detail/members/members.component.spec.ts b/projects/social_platform/src/app/office/program/detail/list/list.component.spec.ts similarity index 54% rename from projects/social_platform/src/app/office/program/detail/members/members.component.spec.ts rename to projects/social_platform/src/app/office/program/detail/list/list.component.spec.ts index aab0c5ae5..53995cf41 100644 --- a/projects/social_platform/src/app/office/program/detail/members/members.component.spec.ts +++ b/projects/social_platform/src/app/office/program/detail/list/list.component.spec.ts @@ -2,22 +2,29 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; -import { ProgramMembersComponent } from "./members.component"; import { RouterTestingModule } from "@angular/router/testing"; +import { of } from "rxjs"; +import { AuthService } from "@auth/services"; import { HttpClientTestingModule } from "@angular/common/http/testing"; +import { ProgramListComponent } from "./list.component"; -describe("MembersComponent", () => { - let component: ProgramMembersComponent; - let fixture: ComponentFixture; +describe("ProgramListComponent", () => { + let component: ProgramListComponent; + let fixture: ComponentFixture; beforeEach(async () => { + const authSpy = { + profile: of({}), + }; + await TestBed.configureTestingModule({ - imports: [RouterTestingModule, HttpClientTestingModule, ProgramMembersComponent], + imports: [RouterTestingModule, HttpClientTestingModule, ProgramListComponent], + providers: [{ provide: AuthService, useValue: authSpy }], }).compileComponents(); }); beforeEach(() => { - fixture = TestBed.createComponent(ProgramMembersComponent); + fixture = TestBed.createComponent(ProgramListComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/projects/social_platform/src/app/office/program/detail/list/list.component.ts b/projects/social_platform/src/app/office/program/detail/list/list.component.ts new file mode 100644 index 000000000..acd8b57c3 --- /dev/null +++ b/projects/social_platform/src/app/office/program/detail/list/list.component.ts @@ -0,0 +1,346 @@ +/** @format */ + +import { CommonModule } from "@angular/common"; +import { + AfterViewInit, + ChangeDetectorRef, + Component, + ElementRef, + inject, + OnDestroy, + OnInit, + Renderer2, + ViewChild, +} from "@angular/core"; +import { + catchError, + concatMap, + distinctUntilChanged, + fromEvent, + map, + noop, + of, + Subscription, + switchMap, + tap, + throttleTime, +} from "rxjs"; +import { ProjectsFilterComponent } from "@office/projects/projects-filter/projects-filter.component"; +import Fuse from "fuse.js"; +import { ActivatedRoute, Router, RouterModule } from "@angular/router"; +import { FormBuilder, FormGroup, ReactiveFormsModule } from "@angular/forms"; +import { SearchComponent } from "@ui/components/search/search.component"; +import { User } from "@auth/models/user.model"; +import { Project } from "@office/models/project.model"; +import { ProjectCardComponent } from "@office/shared/project-card/project-card.component"; +import { MemberCardComponent } from "@office/shared/member-card/member-card.component"; +import { ProgramService } from "@office/program/services/program.service"; +import { AuthService } from "@auth/services"; +import { SubscriptionService } from "@office/services/subscription.service"; +import { ApiPagination } from "@models/api-pagination.model"; +import { HttpParams } from "@angular/common/http"; +import { PartnerProgramFields } from "@office/models/partner-program-fields.model"; + +@Component({ + selector: "app-list", + templateUrl: "./list.component.html", + styleUrl: "./list.component.scss", + imports: [ + CommonModule, + ReactiveFormsModule, + RouterModule, + ProjectsFilterComponent, + SearchComponent, + ProjectCardComponent, + MemberCardComponent, + ], + standalone: true, +}) +export class ProgramListComponent implements OnInit, OnDestroy, AfterViewInit { + constructor() { + this.searchForm = this.fb.group({ + search: [""], + }); + } + + @ViewChild("listRoot") listRoot?: ElementRef; + @ViewChild("filterBody") filterBody!: ElementRef; + + private readonly renderer = inject(Renderer2); + private readonly fb = inject(FormBuilder); + private readonly route = inject(ActivatedRoute); + private readonly router = inject(Router); + private readonly cdref = inject(ChangeDetectorRef); + private readonly programService = inject(ProgramService); + private readonly authService = inject(AuthService); + private readonly subscriptionService = inject(SubscriptionService); + + private previousReqQuery: Record = {}; + private availableFilters: PartnerProgramFields[] = []; + + searchForm: FormGroup; + + listTotalCount?: number; + listPage = 1; + listTake = 20; + perPage = 21; + + list: any[] = []; + searchedList: any[] = []; + profile?: User; + profileProjSubsIds?: number[]; + + listType: "projects" | "members" = "projects"; + + subscriptions$: Subscription[] = []; + + ngOnInit(): void { + // Определяем тип списка + this.route.data.subscribe(data => { + this.listType = data["listType"]; + }); + + // Загружаем начальные данные из резолвера + const routeData$ = this.route.data.pipe(map(r => r["data"])).subscribe(data => { + this.listTotalCount = data.count; + this.list = data.results; + this.searchedList = data.results; + }); + + this.subscriptions$.push(routeData$); + + // Форма поиска + const searchFormSearch$ = this.searchForm.get("search")?.valueChanges.subscribe(search => { + this.router + .navigate([], { + queryParams: { search }, + relativeTo: this.route, + queryParamsHandling: "merge", + }) + .then(() => console.debug("QueryParams changed from ProgramListComponent")); + }); + + searchFormSearch$ && this.subscriptions$.push(searchFormSearch$); + + if (this.listType === "projects") { + const profile$ = this.authService.profile + .pipe( + switchMap(p => { + this.profile = p; + return this.subscriptionService.getSubscriptions(p.id).pipe( + map(resp => { + this.profileProjSubsIds = resp.results.map(sub => sub.id); + }) + ); + }) + ) + .subscribe(); + + profile$ && this.subscriptions$.push(profile$); + } + + const querySearch$ = this.route.queryParams.pipe(map(q => q["search"])).subscribe(search => { + const searchKeys = this.listType === "projects" ? ["name"] : ["firstName", "lastName"]; + + const fuse = new Fuse(this.list, { + keys: searchKeys, + }); + + this.searchedList = search ? fuse.search(search).map(el => el.item) : this.list; + }); + + querySearch$ && this.subscriptions$.push(querySearch$); + + // Фильтры (только для проектов) + if (this.listType === "projects") { + const filtersObservable$ = this.route.queryParams + .pipe( + distinctUntilChanged(), + concatMap(q => { + const reqQuery = this.buildFilterQuery(q); + const programId = this.route.parent?.snapshot.params["programId"]; + + if (JSON.stringify(reqQuery) !== JSON.stringify(this.previousReqQuery)) { + this.previousReqQuery = reqQuery; + + const hasFilters = + reqQuery && reqQuery["filters"] && Object.keys(reqQuery["filters"]).length > 0; + const params = new HttpParams({ fromObject: { offset: 0, limit: this.perPage } }); + + if (hasFilters) { + return this.programService + .createProgramFilters(programId, reqQuery["filters"]) + .pipe( + catchError(err => { + console.error("createFilters failed, fallback to getAllProjects()", err); + return this.programService.getAllProjects(programId, params); + }) + ); + } + + return this.programService.getAllProjects(programId, params).pipe( + catchError(err => { + console.error("getAllProjects failed", err); + return this.programService.getAllProjects(programId, params); + }) + ); + } + + return of(null); + }) + ) + .subscribe(result => { + if (result && typeof result !== "number") { + this.list = result.results; + this.searchedList = result.results; + this.listTotalCount = result.count; + this.cdref.detectChanges(); + } + }); + + this.subscriptions$.push(filtersObservable$); + } + } + + ngAfterViewInit(): void { + const target = document.querySelector(".office__body"); + if (target) { + const scrollEvent$ = fromEvent(target, "scroll") + .pipe( + throttleTime(500), + concatMap(() => this.onScroll()) + ) + .subscribe(noop); + this.subscriptions$.push(scrollEvent$); + } + } + + ngOnDestroy(): void { + this.subscriptions$.forEach($ => $.unsubscribe()); + } + + // Логика бесконечной прокрутки + private onScroll() { + if (this.listTotalCount && this.list.length >= this.listTotalCount) return of({}); + + const target = document.querySelector(".office__body"); + if (!target || !this.listRoot) return of({}); + + const diff = + target.scrollTop - + this.listRoot.nativeElement.getBoundingClientRect().height + + window.innerHeight; + const threshold = this.listType === "projects" ? -200 : 0; + + if (diff > threshold) { + return this.onFetch(); + } + + return of({}); + } + + // Загрузка следующей порции данных + private onFetch() { + const programId = this.route.parent?.snapshot.params["programId"]; + + if (this.listType === "projects") { + const offset = this.listPage * this.perPage; + const params = new HttpParams({ fromObject: { offset, limit: this.perPage } }); + + return this.programService.getAllProjects(programId, params).pipe( + tap((projects: ApiPagination) => { + this.listTotalCount = projects.count; + this.list = [...this.list, ...projects.results]; + this.searchedList = this.list; + this.listPage++; + this.cdref.detectChanges(); + }) + ); + } else { + return this.programService + .getAllMembers(programId, this.listPage * this.listTake, this.listTake) + .pipe( + tap((members: ApiPagination) => { + this.listTotalCount = members.count; + this.list = [...this.list, ...members.results]; + this.searchedList = this.list; + this.listPage++; + this.cdref.detectChanges(); + }) + ); + } + } + + // Построение запроса для фильтров (только для проектов) + private buildFilterQuery(q: any): Record { + if (this.listType !== "projects") return {}; + + const filters: Record = {}; + + if (this.availableFilters.length === 0) { + Object.keys(q).forEach(key => { + if (key !== "search" && q[key] !== undefined && q[key] !== "") { + filters[key] = Array.isArray(q[key]) ? q[key] : [q[key]]; + } + }); + } else { + this.availableFilters.forEach((filter: PartnerProgramFields) => { + const value = q[filter.name]; + if (value !== undefined && value !== "") { + filters[filter.name] = Array.isArray(value) ? value : [value]; + } + }); + } + + return { filters }; + } + + // Обработка загруженных фильтров + onFiltersLoaded(filters: PartnerProgramFields[]): void { + this.availableFilters = filters; + } + + // Swipe логика для мобильных устройств + isFilterOpen = false; + private swipeStartY = 0; + private swipeThreshold = 50; + private isSwiping = false; + + onSwipeStart(event: TouchEvent): void { + this.swipeStartY = event.touches[0].clientY; + this.isSwiping = true; + } + + onSwipeMove(event: TouchEvent): void { + if (!this.isSwiping) return; + + const currentY = event.touches[0].clientY; + const deltaY = currentY - this.swipeStartY; + + const progress = Math.min(deltaY / this.swipeThreshold, 1); + this.renderer.setStyle( + this.filterBody.nativeElement, + "transform", + `translateY(${progress * 100}px)` + ); + } + + onSwipeEnd(event: TouchEvent): void { + if (!this.isSwiping) return; + + const endY = event.changedTouches[0].clientY; + const deltaY = endY - this.swipeStartY; + + if (deltaY > this.swipeThreshold) { + this.closeFilter(); + } + + this.isSwiping = false; + + this.renderer.setStyle(this.filterBody.nativeElement, "transform", "translateY(0)"); + } + + closeFilter(): void { + this.isFilterOpen = false; + } +} diff --git a/projects/social_platform/src/app/office/program/detail/members/members.resolver.ts b/projects/social_platform/src/app/office/program/detail/list/members.resolver.ts similarity index 100% rename from projects/social_platform/src/app/office/program/detail/members/members.resolver.ts rename to projects/social_platform/src/app/office/program/detail/list/members.resolver.ts diff --git a/projects/social_platform/src/app/office/program/detail/projects/projects-filter/projects-filter.component.html b/projects/social_platform/src/app/office/program/detail/list/projects-filter/projects-filter.component.html similarity index 100% rename from projects/social_platform/src/app/office/program/detail/projects/projects-filter/projects-filter.component.html rename to projects/social_platform/src/app/office/program/detail/list/projects-filter/projects-filter.component.html diff --git a/projects/social_platform/src/app/office/program/detail/projects/projects-filter/projects-filter.component.scss b/projects/social_platform/src/app/office/program/detail/list/projects-filter/projects-filter.component.scss similarity index 100% rename from projects/social_platform/src/app/office/program/detail/projects/projects-filter/projects-filter.component.scss rename to projects/social_platform/src/app/office/program/detail/list/projects-filter/projects-filter.component.scss diff --git a/projects/social_platform/src/app/office/program/detail/projects/projects-filter/projects-filter.component.spec.ts b/projects/social_platform/src/app/office/program/detail/list/projects-filter/projects-filter.component.spec.ts similarity index 100% rename from projects/social_platform/src/app/office/program/detail/projects/projects-filter/projects-filter.component.spec.ts rename to projects/social_platform/src/app/office/program/detail/list/projects-filter/projects-filter.component.spec.ts diff --git a/projects/social_platform/src/app/office/program/detail/projects/projects-filter/projects-filter.component.ts b/projects/social_platform/src/app/office/program/detail/list/projects-filter/projects-filter.component.ts similarity index 100% rename from projects/social_platform/src/app/office/program/detail/projects/projects-filter/projects-filter.component.ts rename to projects/social_platform/src/app/office/program/detail/list/projects-filter/projects-filter.component.ts diff --git a/projects/social_platform/src/app/office/program/detail/projects/projects.resolver.ts b/projects/social_platform/src/app/office/program/detail/list/projects.resolver.ts similarity index 95% rename from projects/social_platform/src/app/office/program/detail/projects/projects.resolver.ts rename to projects/social_platform/src/app/office/program/detail/list/projects.resolver.ts index c474a63ce..ec081377d 100644 --- a/projects/social_platform/src/app/office/program/detail/projects/projects.resolver.ts +++ b/projects/social_platform/src/app/office/program/detail/list/projects.resolver.ts @@ -1,7 +1,7 @@ /** @format */ import { inject } from "@angular/core"; -import { ActivatedRoute, ActivatedRouteSnapshot, ResolveFn, Router } from "@angular/router"; +import { ActivatedRouteSnapshot, ResolveFn, Router } from "@angular/router"; import { ProgramService } from "@office/program/services/program.service"; import { Project } from "@models/project.model"; import { ApiPagination } from "@models/api-pagination.model"; diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.scss b/projects/social_platform/src/app/office/program/detail/main/main.component.scss index 891ed89e2..5babebf3a 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.scss +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.scss @@ -18,6 +18,10 @@ flex-direction: column; } + &__left { + width: 157px; + } + &__section { padding: 24px; margin-bottom: 14px; diff --git a/projects/social_platform/src/app/office/program/detail/members/members.component.html b/projects/social_platform/src/app/office/program/detail/members/members.component.html deleted file mode 100644 index af2b6882b..000000000 --- a/projects/social_platform/src/app/office/program/detail/members/members.component.html +++ /dev/null @@ -1,41 +0,0 @@ - - -
-
- - -
- Фильтр - -
- -
    - @for (m of searchedMembers; track m.id) { - - - - } -
-
- -
-
-
-
-
-
- -
-
-
-
-
diff --git a/projects/social_platform/src/app/office/program/detail/members/members.component.ts b/projects/social_platform/src/app/office/program/detail/members/members.component.ts deleted file mode 100644 index 25d184039..000000000 --- a/projects/social_platform/src/app/office/program/detail/members/members.component.ts +++ /dev/null @@ -1,229 +0,0 @@ -/** @format */ - -import { - AfterViewInit, - ChangeDetectorRef, - Component, - ElementRef, - OnInit, - Renderer2, - ViewChild, -} from "@angular/core"; -import { ActivatedRoute, Params, RouterLink } from "@angular/router"; -import { - concatMap, - distinctUntilChanged, - fromEvent, - map, - noop, - Observable, - of, - Subscription, - tap, - throttleTime, -} from "rxjs"; -import { Program } from "@office/program/models/program.model"; -import { User } from "@auth/models/user.model"; -import { ProgramService } from "@office/program/services/program.service"; -import { MemberCardComponent } from "@office/shared/member-card/member-card.component"; -import { AsyncPipe, CommonModule } from "@angular/common"; -import { ApiPagination } from "@models/api-pagination.model"; -import { ProjectCardComponent } from "@office/shared/project-card/project-card.component"; -import { ProjectsFilterComponent } from "@office/projects/projects-filter/projects-filter.component"; -import { SearchComponent } from "@ui/components/search/search.component"; -import { FormBuilder, FormGroup, ReactiveFormsModule } from "@angular/forms"; -import Fuse from "fuse.js"; - -/** - * Компонент списка участников программы - * - * Отображает всех участников программы с поддержкой: - * - Бесконечной прокрутки для подгрузки участников - * - Адаптивного дизайна - * - Интеграции с заголовком программы - * - * Принимает: - * @param {ActivatedRoute} route - Для получения данных из резолвера - * @param {ChangeDetectorRef} cdref - Для ручного обновления представления - * @param {ProgramService} programService - Сервис для загрузки участников - * - * Данные: - * @property {User[]} members - Массив участников программы - * @property {number} membersTotalCount - Общее количество участников - * @property {Observable} program$ - Поток данных программы - * @property {Observable} members$ - Поток участников из резолвера - * - * Пагинация: - * @property {number} membersPage - Текущая страница - * @property {number} membersTake - Количество участников на странице (20) - * - * ViewChild: - * @ViewChild membersRoot - Ссылка на DOM элемент списка участников - * - * Жизненный цикл: - * - OnInit: Загружает начальные данные из резолвера - * - AfterViewInit: Настраивает обработчик прокрутки - * - * Методы: - * @method onScroll() - Проверяет необходимость подгрузки данных - * @method onFetch() - Загружает следующую порцию участников - * - * Возвращает: - * HTML шаблон со списком карточек участников - */ -@Component({ - selector: "app-members", - templateUrl: "./members.component.html", - styleUrl: "./members.component.scss", - standalone: true, - imports: [ - ReactiveFormsModule, - CommonModule, - RouterLink, - MemberCardComponent, - ProjectsFilterComponent, - SearchComponent, - ], -}) -export class ProgramMembersComponent implements OnInit, AfterViewInit { - constructor( - private readonly route: ActivatedRoute, - private readonly cdref: ChangeDetectorRef, - private readonly fb: FormBuilder, - private readonly renderer: Renderer2, - private readonly programService: ProgramService - ) { - this.searchForm = this.fb.group({ - search: [""], - }); - } - - searchForm: FormGroup; - - members: User[] = []; - searchedMembers: User[] = []; - - membersTotalCount?: number; - membersPage = 1; - membersTake = 20; - - subscriptions$: Subscription[] = []; - private previousReqQuery: Record = {}; - - @ViewChild("membersRoot") membersRoot?: ElementRef; - @ViewChild("filterBody") filterBody!: ElementRef; - - ngOnInit(): void { - this.route.data.pipe(map(r => r["data"])).subscribe((members: ApiPagination) => { - this.membersTotalCount = members.count; - this.members = members.results; - this.searchedMembers = members.results; - }); - - const querySearch$ = this.route.queryParams.pipe(map(q => q["search"])).subscribe(search => { - const fuse = new Fuse(this.members, { - keys: ["name"], - }); - - this.searchedMembers = search ? fuse.search(search).map(el => el.item) : this.members; - }); - - querySearch$ && this.subscriptions$.push(querySearch$); - } - - ngAfterViewInit(): void { - const target = document.querySelector(".office__body"); - if (target) - fromEvent(target, "scroll") - .pipe( - concatMap(() => this.onScroll()), - throttleTime(500) - ) - .subscribe(noop); - } - - ngOnDestroy(): void { - this.subscriptions$.forEach($ => $.unsubscribe()); - } - - onScroll() { - if (this.membersTotalCount && this.members.length >= this.membersTotalCount) return of({}); - - const target = document.querySelector(".office__body"); - if (!target || !this.membersRoot) return of({}); - - const diff = - target.scrollTop - - this.membersRoot.nativeElement.getBoundingClientRect().height + - window.innerHeight; - - if (diff > 0) { - return this.onFetch(); - } - - return of({}); - } - - onFetch() { - return this.programService - .getAllMembers( - this.route.parent?.snapshot.params["programId"], - this.membersPage * this.membersTake, - this.membersTake - ) - .pipe( - tap((members: ApiPagination) => { - this.membersTotalCount = members.count; - this.members = [...this.members, ...members.results]; - - this.membersPage++; - - this.cdref.detectChanges(); - }) - ); - } - - isFilterOpen = false; - - private swipeStartY = 0; - private swipeThreshold = 50; - private isSwiping = false; - - onSwipeStart(event: TouchEvent): void { - this.swipeStartY = event.touches[0].clientY; - this.isSwiping = true; - } - - onSwipeMove(event: TouchEvent): void { - if (!this.isSwiping) return; - - const currentY = event.touches[0].clientY; - const deltaY = currentY - this.swipeStartY; - - const progress = Math.min(deltaY / this.swipeThreshold, 1); - this.renderer.setStyle( - this.filterBody.nativeElement, - "transform", - `translateY(${progress * 100}px)` - ); - } - - onSwipeEnd(event: TouchEvent): void { - if (!this.isSwiping) return; - - const endY = event.changedTouches[0].clientY; - const deltaY = endY - this.swipeStartY; - - if (deltaY > this.swipeThreshold) { - this.closeFilter(); - } - - this.isSwiping = false; - - this.renderer.setStyle(this.filterBody.nativeElement, "transform", "translateY(0)"); - } - - closeFilter(): void { - this.isFilterOpen = false; - } -} diff --git a/projects/social_platform/src/app/office/program/detail/members/members.resolver.spec.ts b/projects/social_platform/src/app/office/program/detail/members/members.resolver.spec.ts deleted file mode 100644 index 66c58851c..000000000 --- a/projects/social_platform/src/app/office/program/detail/members/members.resolver.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** @format */ - -import { TestBed } from "@angular/core/testing"; -import { ProgramMembersResolver } from "./members.resolver"; -import { HttpClientTestingModule } from "@angular/common/http/testing"; -import { ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router"; - -describe("ProgramMembersResolver", () => { - const mockRoute = { parent: { params: { programId: 1 } } } as unknown as ActivatedRouteSnapshot; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - }); - }); - - it("should be created", () => { - const result = TestBed.runInInjectionContext(() => - ProgramMembersResolver(mockRoute, {} as RouterStateSnapshot) - ); - expect(result).toBeTruthy(); - }); -}); diff --git a/projects/social_platform/src/app/office/program/detail/projects/projects.component.scss b/projects/social_platform/src/app/office/program/detail/projects/projects.component.scss deleted file mode 100644 index 3a19b51a3..000000000 --- a/projects/social_platform/src/app/office/program/detail/projects/projects.component.scss +++ /dev/null @@ -1,146 +0,0 @@ -@use "styles/responsive"; -@use "styles/typography"; - -.page { - display: grid; - grid-template-columns: 8fr 2fr; - gap: 20px; - padding-bottom: 100px; - - &__search { - flex-grow: 1; - } - - &__outlet { - display: flex; - flex-direction: column; - flex-grow: 1; - gap: 20px; - width: 100%; - margin-top: 10px; - } - - &__filter { - display: none; - - &--open { - display: block; - } - - @include responsive.apply-desktop { - display: block; - margin-left: 16px; - } - } - - &__list { - display: grid; - flex-grow: 1; - grid-template-columns: 1fr; - row-gap: 50px; - column-gap: 20px; - align-items: flex-start; - margin-top: 50px; - - @include responsive.apply-desktop { - grid-template-columns: repeat(4, 2fr); - } - } - - &__create { - display: flex; - flex-direction: column; - gap: 20px; - align-items: center; - margin-top: 20px; - } - - &__left { - width: 100%; - } -} - -.filter { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 10; - - @include responsive.apply-desktop { - position: static; - } - - &__overlay { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background-color: black; - opacity: 0.3; - - @include responsive.apply-desktop { - display: none; - } - } - - &__bar { - position: fixed; - display: flex; - width: 100%; - height: 25px; - touch-action: none; - - @include responsive.apply-desktop { - display: none; - } - - &::after { - display: block; - width: 85px; - height: 5px; - margin: auto; - content: ""; - background-color: var(--gray); - border-radius: var(--rounded-lg); - transition: transform 0.2s; - } - } - - &__body { - position: absolute; - right: 0; - bottom: 0; - left: 0; - z-index: 10; - max-height: 72vh; - overflow-y: auto; - background-color: var(--white); - border-radius: var(--rounded-lg); - transform: translateY(0%); - - @include responsive.apply-desktop { - position: static; - max-height: unset; - transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); - } - } -} - -.filter-toggle { - position: relative; - display: flex; - align-items: center; - justify-content: space-between; - padding: 15px 10px; - cursor: pointer; - background-color: var(--white); - border: 1px solid var(--medium-medium-grey-for-outline); - border-radius: var(--rounded-xl); - - @include responsive.apply-desktop { - display: none; - } -} diff --git a/projects/social_platform/src/app/office/program/detail/projects/projects.component.spec.ts b/projects/social_platform/src/app/office/program/detail/projects/projects.component.spec.ts deleted file mode 100644 index ca8d7a7ca..000000000 --- a/projects/social_platform/src/app/office/program/detail/projects/projects.component.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** @format */ - -import { ComponentFixture, TestBed } from "@angular/core/testing"; - -import { ProgramProjectsComponent } from "./projects.component"; -import { RouterTestingModule } from "@angular/router/testing"; -import { HttpClientTestingModule } from "@angular/common/http/testing"; - -describe("ProjectsComponent", () => { - let component: ProgramProjectsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [RouterTestingModule, ProgramProjectsComponent, HttpClientTestingModule], - }).compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(ProgramProjectsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it("should create", () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/projects/social_platform/src/app/office/program/detail/projects/projects.component.ts b/projects/social_platform/src/app/office/program/detail/projects/projects.component.ts deleted file mode 100644 index 44f39a9dc..000000000 --- a/projects/social_platform/src/app/office/program/detail/projects/projects.component.ts +++ /dev/null @@ -1,354 +0,0 @@ -/** @format */ - -import { - AfterViewInit, - ChangeDetectorRef, - Component, - ElementRef, - OnDestroy, - OnInit, - Renderer2, - ViewChild, -} from "@angular/core"; -import { ActivatedRoute, Params, Router, RouterLink } from "@angular/router"; -import { - catchError, - concatMap, - distinctUntilChanged, - fromEvent, - map, - noop, - Observable, - of, - Subscription, - switchMap, - tap, - throttleTime, -} from "rxjs"; -import { Project } from "@models/project.model"; -import { Program } from "@office/program/models/program.model"; -import { ProjectCardComponent } from "@office/shared/project-card/project-card.component"; -import { AsyncPipe, CommonModule } from "@angular/common"; -import { ProgramService } from "@office/program/services/program.service"; -import { AuthService } from "@auth/services"; -import { FormBuilder, FormGroup, ReactiveFormsModule } from "@angular/forms"; -import { SearchComponent } from "@ui/components/search/search.component"; -import Fuse from "fuse.js"; -import { ProjectsFilterComponent } from "@office/program/detail/projects/projects-filter/projects-filter.component"; -import { HttpParams } from "@angular/common/http"; -import { IconComponent } from "@uilib"; -import { PartnerProgramFields } from "@office/models/partner-program-fields.model"; -import { SubscriptionService } from "@office/services/subscription.service"; -import { User } from "@auth/models/user.model"; - -/** - * Компонент списка проектов программы - * - * Отображает все проекты, связанные с программой, с поддержкой: - * - Бесконечной прокрутки (infinite scroll) - * - Пагинации данных - * - Модального окна для оценки проектов (для экспертов) - * - * Принимает: - * @param {ActivatedRoute} route - Для получения данных из резолвера - * @param {ProgramService} programService - Сервис для загрузки проектов - * @param {AuthService} authService - Для проверки прав пользователя - * - * Данные: - * @property {Project[]} projects - Массив проектов программы - * @property {number} projectsTotalCount - Общее количество проектов - * @property {Observable} program$ - Поток данных программы - * @property {number} page - Текущая страница пагинации - * @property {number} perPage - Количество проектов на странице - * @property {boolean} isFilterOpen - состояние панели фильтров (мобильные) - * ViewChild: - * @ViewChild projectsRoot - Ссылка на DOM элемент списка проектов - * - * Функциональность: - * - Загрузка начальных данных из резолвера - * - Автоматическая подгрузка при прокрутке - * - Отображение карточек проектов - * - Интеграция с компонентом оценки проектов - * - * Методы: - * @method onScroll() - Обработчик прокрутки для подгрузки данных - * @method onFetch() - Загрузка следующей порции проектов - * - * Возвращает: - * HTML шаблон со списком проектов и элементами управления - */ -@Component({ - selector: "app-projects", - templateUrl: "./projects.component.html", - styleUrl: "./projects.component.scss", - standalone: true, - imports: [ - CommonModule, - ReactiveFormsModule, - RouterLink, - ProjectCardComponent, - IconComponent, - AsyncPipe, - SearchComponent, - ProjectsFilterComponent, - ], -}) -export class ProgramProjectsComponent implements OnInit, AfterViewInit, OnDestroy { - constructor( - private readonly route: ActivatedRoute, - private readonly router: Router, - private readonly fb: FormBuilder, - private readonly cdref: ChangeDetectorRef, - private readonly programService: ProgramService, - private readonly subscriptionService: SubscriptionService, - public readonly authService: AuthService, - private readonly renderer: Renderer2 - ) { - this.searchForm = this.fb.group({ - search: [""], - }); - } - - @ViewChild("projectsRoot") projectsRoot?: ElementRef; - @ViewChild("filterBody") filterBody!: ElementRef; - - projectsTotalCount?: number; - page = 1; - perPage = 21; - - profile?: User; - projects: Project[] = []; - searchedProjects: Project[] = []; - profileProjSubsIds?: number[]; - - searchForm: FormGroup; - subscriptions$: Subscription[] = []; - - private previousReqQuery: Record = {}; - private availableFilters: PartnerProgramFields[] = []; - - ngOnInit(): void { - const routeData$ = this.route.data - .pipe( - map(r => r["data"]), - tap(r => (this.projectsTotalCount = r["count"])), - map(r => r["results"]) - ) - .subscribe({ - next: projects => { - this.projects = projects; - this.searchedProjects = projects; - }, - }); - - const searchFormSearch$ = this.searchForm.get("search")?.valueChanges.subscribe(search => { - this.router - .navigate([], { - queryParams: { search }, - relativeTo: this.route, - queryParamsHandling: "merge", - }) - .then(() => console.debug("QueryParams changed from ProjectsComponent")); - }); - - searchFormSearch$ && this.subscriptions$.push(searchFormSearch$); - - const profile$ = this.authService.profile - .pipe( - switchMap(p => { - this.profile = p; - return this.subscriptionService.getSubscriptions(p.id).pipe( - map(resp => { - this.profileProjSubsIds = resp.results.map(sub => sub.id); - }) - ); - }) - ) - .subscribe(); - - profile$ && this.subscriptions$.push(profile$); - - const querySearch$ = this.route.queryParams.pipe(map(q => q["search"])).subscribe(search => { - const fuse = new Fuse(this.projects, { - keys: ["name"], - }); - - this.searchedProjects = search ? fuse.search(search).map(el => el.item) : this.projects; - }); - - querySearch$ && this.subscriptions$.push(querySearch$); - - const observable = this.route.queryParams.pipe( - distinctUntilChanged(), - concatMap(q => { - const reqQuery = this.buildFilterQuery(q); - const programId = this.route.parent?.snapshot.params["programId"]; - - if (JSON.stringify(reqQuery) !== JSON.stringify(this.previousReqQuery)) { - this.previousReqQuery = reqQuery; - - const hasFilters = - reqQuery && reqQuery["filters"] && Object.keys(reqQuery["filters"]).length > 0; - - const params = new HttpParams({ fromObject: { offset: 0, limit: 21 } }); - - if (hasFilters) { - return this.programService.createProgramFilters(programId, reqQuery["filters"]).pipe( - catchError(err => { - console.error("createFilters failed, fallback to getAllProjects()", err); - return this.programService.getAllProjects(programId, params); - }) - ); - } - - return this.programService.getAllProjects(programId, params).pipe( - catchError(err => { - console.error("getAllProjects failed", err); - return this.programService.getAllProjects(programId, params); - }) - ); - } - - this.previousReqQuery = reqQuery; - return of(0); - }) - ); - - const projects$ = observable.subscribe(projects => { - if (typeof projects === "number") return; - - this.projects = projects.results; - this.searchedProjects = projects.results; - - this.cdref.detectChanges(); - }); - - projects$ && this.subscriptions$.push(projects$); - - this.subscriptions$.push(routeData$); - } - - ngAfterViewInit() { - const target = document.querySelector(".office__body"); - if (!target) return; - - const scroll$ = fromEvent(target, "scroll") - .pipe( - throttleTime(500), - concatMap(() => this.onScroll()) - ) - .subscribe(noop); - this.subscriptions$.push(scroll$); - } - - ngOnDestroy(): void { - this.subscriptions$.forEach($ => $.unsubscribe()); - } - - isFilterOpen = false; - - private swipeStartY = 0; - private swipeThreshold = 50; - private isSwiping = false; - - onSwipeStart(event: TouchEvent): void { - this.swipeStartY = event.touches[0].clientY; - this.isSwiping = true; - } - - onSwipeMove(event: TouchEvent): void { - if (!this.isSwiping) return; - - const currentY = event.touches[0].clientY; - const deltaY = currentY - this.swipeStartY; - - const progress = Math.min(deltaY / this.swipeThreshold, 1); - this.renderer.setStyle( - this.filterBody.nativeElement, - "transform", - `translateY(${progress * 100}px)` - ); - } - - onSwipeEnd(event: TouchEvent): void { - if (!this.isSwiping) return; - - const endY = event.changedTouches[0].clientY; - const deltaY = endY - this.swipeStartY; - - if (deltaY > this.swipeThreshold) { - this.closeFilter(); - } - - this.isSwiping = false; - - this.renderer.setStyle(this.filterBody.nativeElement, "transform", "translateY(0)"); - } - - closeFilter(): void { - this.isFilterOpen = false; - } - - onFiltersLoaded(filters: PartnerProgramFields[]): void { - this.availableFilters = filters; - } - - private onScroll() { - if (this.projectsTotalCount && this.projects.length >= this.projectsTotalCount) return of({}); - - const target = document.querySelector(".office__body"); - if (!target || !this.projectsRoot) return of({}); - - const diff = - target.scrollTop - - this.projectsRoot.nativeElement.getBoundingClientRect().height + - window.innerHeight; - - if (diff > -200) { - return this.onFetch(); - } - - return of({}); - } - - private onFetch() { - const programId = this.route.parent?.snapshot.params["programId"]; - const offset = this.page * this.perPage; - const limit = this.perPage; - - return this.programService - .getAllProjects(programId, new HttpParams({ fromObject: { offset, limit } })) - .pipe( - tap(projects => { - this.projectsTotalCount = projects.count; - this.projects = [...this.projects, ...projects.results]; - this.searchedProjects = this.projects; - - this.page++; - - this.cdref.detectChanges(); - }) - ); - } - - private buildFilterQuery(q: Params): Record { - const filters: Record = {}; - - if (this.availableFilters.length === 0) { - Object.keys(q).forEach(key => { - if (key !== "search" && q[key] !== undefined && q[key] !== "") { - filters[key] = Array.isArray(q[key]) ? q[key] : [q[key]]; - } - }); - } else { - this.availableFilters.forEach((filter: PartnerProgramFields) => { - const value = q[filter.name]; - if (value !== undefined && value !== "") { - filters[filter.name] = Array.isArray(value) ? value : [value]; - } - }); - } - - return { filters }; - } -} diff --git a/projects/social_platform/src/app/office/program/detail/projects/projects.resolver.spec.ts b/projects/social_platform/src/app/office/program/detail/projects/projects.resolver.spec.ts deleted file mode 100644 index c3de2576a..000000000 --- a/projects/social_platform/src/app/office/program/detail/projects/projects.resolver.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** @format */ - -import { TestBed } from "@angular/core/testing"; - -import { ProgramProjectsResolver } from "./projects.resolver"; -import { HttpClientTestingModule } from "@angular/common/http/testing"; -import { ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router"; - -describe("ProgramProjectsResolver", () => { - const mockRoute = { parent: { params: { programId: 1 } } } as unknown as ActivatedRouteSnapshot; - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - }); - }); - - it("should be created", () => { - const result = TestBed.runInInjectionContext(() => - ProgramProjectsResolver(mockRoute, {} as RouterStateSnapshot) - ); - expect(result).toBeTruthy(); - }); -}); diff --git a/projects/social_platform/src/app/office/program/detail/rate-projects/list-all.resolver.spec.ts b/projects/social_platform/src/app/office/program/detail/rate-projects/list-all.resolver.spec.ts index e47b298d5..880c3dc7b 100644 --- a/projects/social_platform/src/app/office/program/detail/rate-projects/list-all.resolver.spec.ts +++ b/projects/social_platform/src/app/office/program/detail/rate-projects/list-all.resolver.spec.ts @@ -2,7 +2,7 @@ import { TestBed } from "@angular/core/testing"; -import { ListAllResolver } from "./list-all.resolver"; +import { ListAllResolver } from "../list/list-all.resolver"; import { HttpClientTestingModule } from "@angular/common/http/testing"; import { ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router"; diff --git a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-project.routes.ts b/projects/social_platform/src/app/office/program/detail/rate-projects/rate-project.routes.ts index b8fd9ad10..28129b7f5 100644 --- a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-project.routes.ts +++ b/projects/social_platform/src/app/office/program/detail/rate-projects/rate-project.routes.ts @@ -3,7 +3,7 @@ import { Routes } from "@angular/router"; import { RateProjectsComponent } from "@office/program/detail/rate-projects/rate-projects.component"; import { ListComponent } from "./list/list.component"; -import { ListAllResolver } from "./list-all.resolver"; +import { ListAllResolver } from "../list/list-all.resolver"; // import { ListRatedResolver } from "./list/list-rated.resolver"; /** diff --git a/projects/social_platform/src/app/office/program/list/main/main.component.html b/projects/social_platform/src/app/office/program/main/main.component.html similarity index 100% rename from projects/social_platform/src/app/office/program/list/main/main.component.html rename to projects/social_platform/src/app/office/program/main/main.component.html diff --git a/projects/social_platform/src/app/office/program/list/main/main.component.scss b/projects/social_platform/src/app/office/program/main/main.component.scss similarity index 100% rename from projects/social_platform/src/app/office/program/list/main/main.component.scss rename to projects/social_platform/src/app/office/program/main/main.component.scss diff --git a/projects/social_platform/src/app/office/program/list/main/main.component.spec.ts b/projects/social_platform/src/app/office/program/main/main.component.spec.ts similarity index 100% rename from projects/social_platform/src/app/office/program/list/main/main.component.spec.ts rename to projects/social_platform/src/app/office/program/main/main.component.spec.ts diff --git a/projects/social_platform/src/app/office/program/list/main/main.component.ts b/projects/social_platform/src/app/office/program/main/main.component.ts similarity index 97% rename from projects/social_platform/src/app/office/program/list/main/main.component.ts rename to projects/social_platform/src/app/office/program/main/main.component.ts index 557c26be0..5f479ff4e 100644 --- a/projects/social_platform/src/app/office/program/list/main/main.component.ts +++ b/projects/social_platform/src/app/office/program/main/main.component.ts @@ -4,12 +4,12 @@ import { Component, OnDestroy, OnInit, signal } from "@angular/core"; import { ActivatedRoute, RouterLink } from "@angular/router"; import { map, Subscription } from "rxjs"; import { Program } from "@office/program/models/program.model"; -import { ProgramCardComponent } from "../../shared/program-card/program-card.component"; import { NavService } from "@office/services/nav.service"; import Fuse from "fuse.js"; import { CheckboxComponent, SelectComponent } from "@ui/components"; import { generateOptionsList } from "@utils/generate-options-list"; import { ClickOutsideModule } from "ng-click-outside"; +import { ProgramCardComponent } from "../shared/program-card/program-card.component"; /** * Главный компонент списка программ diff --git a/projects/social_platform/src/app/office/program/list/main/main.resolver.spec.ts b/projects/social_platform/src/app/office/program/main/main.resolver.spec.ts similarity index 100% rename from projects/social_platform/src/app/office/program/list/main/main.resolver.spec.ts rename to projects/social_platform/src/app/office/program/main/main.resolver.spec.ts diff --git a/projects/social_platform/src/app/office/program/list/main/main.resolver.ts b/projects/social_platform/src/app/office/program/main/main.resolver.ts similarity index 100% rename from projects/social_platform/src/app/office/program/list/main/main.resolver.ts rename to projects/social_platform/src/app/office/program/main/main.resolver.ts diff --git a/projects/social_platform/src/app/office/program/program.routes.ts b/projects/social_platform/src/app/office/program/program.routes.ts index 9f4c84f78..e1a17341e 100644 --- a/projects/social_platform/src/app/office/program/program.routes.ts +++ b/projects/social_platform/src/app/office/program/program.routes.ts @@ -2,8 +2,8 @@ import { Routes } from "@angular/router"; import { ProgramComponent } from "./program.component"; -import { ProgramMainComponent } from "./list/main/main.component"; -import { ProgramMainResolver } from "./list/main/main.resolver"; +import { ProgramMainComponent } from "./main/main.component"; +import { ProgramMainResolver } from "./main/main.resolver"; /** * Конфигурация маршрутов для модуля "Программы" diff --git a/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.html b/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.html index 03bb316ff..a0746227d 100644 --- a/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.html +++ b/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.html @@ -57,7 +57,6 @@
-
diff --git a/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.scss b/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.scss index e66f90877..3a5755d5e 100644 --- a/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.scss +++ b/projects/social_platform/src/app/office/program/shared/rating-card/rating-card.component.scss @@ -53,6 +53,7 @@ display: flex; gap: 4px; align-items: center; + margin-top: 14px; &--icon { ::ng-deep { @@ -78,7 +79,6 @@ &__lower { display: flex; flex-direction: column; - gap: 14px; &--info { display: flex; diff --git a/projects/social_platform/src/app/office/projects/dashboard/dashboard.component.html b/projects/social_platform/src/app/office/projects/dashboard/dashboard.component.html index 0770d3e08..c26f9e315 100644 --- a/projects/social_platform/src/app/office/projects/dashboard/dashboard.component.html +++ b/projects/social_platform/src/app/office/projects/dashboard/dashboard.component.html @@ -1,23 +1,13 @@
+ @for (dashboardItem of dashboardItems; track $index) { - - + }
diff --git a/projects/social_platform/src/app/office/projects/dashboard/dashboard.component.ts b/projects/social_platform/src/app/office/projects/dashboard/dashboard.component.ts index 3c9ade462..7a6b05d9b 100644 --- a/projects/social_platform/src/app/office/projects/dashboard/dashboard.component.ts +++ b/projects/social_platform/src/app/office/projects/dashboard/dashboard.component.ts @@ -6,6 +6,7 @@ import { ActivatedRoute, Router } from "@angular/router"; import { Project } from "@office/models/project.model"; import { Subscription } from "rxjs"; import { DashboardItemComponent } from "./shared/dashboardItem/dashboardItem.component"; +import { DashboardItem, dashboardItemBuilder } from "@utils/dashboardItemBuilder"; @Component({ selector: "app-dashboard", @@ -16,11 +17,8 @@ import { DashboardItemComponent } from "./shared/dashboardItem/dashboardItem.com }) export class DashboardProjectsComponent implements OnInit, OnDestroy { private readonly route = inject(ActivatedRoute); - private readonly router = inject(Router); - allProjects: Project[] = []; - myProjects: Project[] = []; - mySubs: Project[] = []; + dashboardItems: DashboardItem[] = []; profileProjSubsIds?: number[]; subscriptions$: Subscription[] = []; @@ -28,10 +26,18 @@ export class DashboardProjectsComponent implements OnInit, OnDestroy { ngOnInit(): void { this.route.data.subscribe({ next: ({ data: { all, my, subs } }) => { - this.allProjects = all.results.slice(0, 4); - this.myProjects = my.results.filter((project: Project) => !project.draft).slice(0, 4); - this.mySubs = subs.results.slice(0, 4); + const allProjects = all.results.slice(0, 4); + const myProjects = my.results.filter((project: Project) => !project.draft).slice(0, 4); + const mySubs = subs.results.slice(0, 4); this.profileProjSubsIds = subs.results.map((project: Project) => project.id); + + this.dashboardItems = dashboardItemBuilder( + 3, + ["my", "subscriptions", "all"], + ["мои проекты", "мои подписки", "витрина проектов"], + ["main", "favourities", "folders"], + [myProjects, mySubs, allProjects] + ); }, }); } diff --git a/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.html b/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.html index 301e9a8ef..02f5ede33 100644 --- a/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.html +++ b/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.html @@ -19,8 +19,18 @@ } } @else { -
- +
+
} diff --git a/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.ts b/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.ts index b287a70cf..a201c9dc7 100644 --- a/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.ts +++ b/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.ts @@ -5,8 +5,8 @@ import { Component, inject, Input, OnInit } from "@angular/core"; import { Project } from "@office/models/project.model"; import { ProjectCardComponent } from "@office/shared/project-card/project-card.component"; import { IconComponent } from "@uilib"; -import { RouterLink } from "@angular/router"; import { ProjectsService } from "@office/projects/services/projects.service"; +import { RouterLink } from "@angular/router"; @Component({ selector: "app-dashboard-item", @@ -30,6 +30,7 @@ export class DashboardItemComponent implements OnInit { switch (this.iconName) { case "favourities": this.appereance = "subs"; + break; case "main": diff --git a/projects/social_platform/src/app/office/projects/edit/edit.component.scss b/projects/social_platform/src/app/office/projects/edit/edit.component.scss index d143d0c0c..f7a92fd0e 100644 --- a/projects/social_platform/src/app/office/projects/edit/edit.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/edit.component.scss @@ -5,7 +5,7 @@ .project { position: relative; - padding: 80px 0; + padding: 30px 0; background-color: var(--white); border-radius: var(--rounded-md); @@ -13,13 +13,15 @@ position: sticky; top: 10%; left: 6%; - z-index: 10; + z-index: 100; display: flex; gap: 12%; + margin-top: 20px; + padding: 4px 0px; align-items: center; justify-content: space-evenly; width: 100%; - background-color: var(--white); + background-color: var(--light-white); border-radius: var(--rounded-xxl); } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.scss index 772e5a54b..abc841229 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.scss @@ -17,7 +17,7 @@ align-items: center; justify-content: center; padding: 2px 10px; - background-color: var(--gray); + background-color: var(--medium-grey-for-outline); border-radius: var(--rounded-xxl); @include responsive.apply-desktop { diff --git a/projects/social_platform/src/app/office/projects/list/list.component.ts b/projects/social_platform/src/app/office/projects/list/list.component.ts index b5b26b827..3132e5e61 100644 --- a/projects/social_platform/src/app/office/projects/list/list.component.ts +++ b/projects/social_platform/src/app/office/projects/list/list.component.ts @@ -31,12 +31,9 @@ import { NavService } from "@services/nav.service"; import { ProjectService } from "@services/project.service"; import { HttpParams } from "@angular/common/http"; import { ApiPagination } from "@models/api-pagination.model"; -import { ProjectsFilterComponent } from "../projects-filter/projects-filter.component"; import { ProjectCardComponent } from "../../shared/project-card/project-card.component"; import { IconComponent } from "@ui/components"; import { SubscriptionService } from "@office/services/subscription.service"; -import { Invite } from "@office/models/invite.model"; -import { InviteService } from "@office/services/invite.service"; import { inviteToProjectMapper } from "@utils/inviteToProjectMapper"; /** diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.html b/projects/social_platform/src/app/office/shared/project-card/project-card.component.html index 3afcf6739..213def7dc 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.html +++ b/projects/social_platform/src/app/office/shared/project-card/project-card.component.html @@ -2,10 +2,16 @@
-
+
@if (shouldShowProjectInfo()) {
@@ -32,7 +38,7 @@ -
+
@if (appereance === 'empty') { @@ -48,9 +54,9 @@ } @@ -62,12 +68,16 @@

{{ project?.name }}

- Стрелка + arrow + @if (section === 'subscriptions') { +

перейти к проектам

+ } @else {

создай первый проект

+ }
-

{{ project?.name || "Без названия" }}

+

{{ project?.name }}

@if (industryService.industries | async; as industries) {

@if (industryService.getIndustry(industries, project?.industry!); as industry) { diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss b/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss index d9c90768e..7e6ed92de 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss +++ b/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss @@ -13,6 +13,11 @@ background-color: var(--light-white); border: 0.5px solid var(--medium-for-outline); border-radius: var(--rounded-xl); + + &--empty { + background: transparent; + border: 0.5px dashed var(--accent); + } } &__info { @@ -61,6 +66,10 @@ padding-top: 40px; overflow: hidden; text-align: center; + + &--empty { + padding-top: 60px; + } } &__head { @@ -128,10 +137,9 @@ &__empty { position: relative; padding: 35px 0 12px; - margin-top: 50px; + margin-top: 20px; cursor: pointer; background: transparent; - border: 0.5px dashed var(--accent); opacity: 1; transition: opacity 0.2s ease; diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.ts b/projects/social_platform/src/app/office/shared/project-card/project-card.component.ts index 7f5aa2869..d3a912e4f 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.ts +++ b/projects/social_platform/src/app/office/shared/project-card/project-card.component.ts @@ -10,6 +10,7 @@ import { ModalComponent } from "@ui/components/modal/modal.component"; import { SubscriptionService } from "@office/services/subscription.service"; import { InviteService } from "@office/services/invite.service"; import { ClickOutsideModule } from "ng-click-outside"; +import { Router } from "@angular/router"; /** * Компонент карточки проекта @@ -33,10 +34,12 @@ export class ProjectCardComponent implements OnInit { private readonly inviteService = inject(InviteService); private readonly subscriptionService = inject(SubscriptionService); public readonly industryService = inject(IndustryService); + private readonly router = inject(Router); @Input() project?: Project; @Input() type: "invite" | "project" = "project"; @Input() appereance: "my" | "subs" | "base" | "empty" = "base"; + @Input() section: "projects" | "subscriptions" | "other" = "projects"; @Input() canDelete?: boolean | null = false; @Input() isSubscribed?: boolean | null = false; @Input() profileId?: number; @@ -77,7 +80,13 @@ export class ProjectCardComponent implements OnInit { * Возвращает URL для аватара */ getAvatarUrl(): string { - return this.project?.imageAddress || "/assets/images/projects/shared/add-project.svg"; + const currentImageAddress = + this.appereance === "empty" && this.section === "projects" + ? "/assets/images/projects/shared/add-project.svg" + : this.appereance === "empty" && this.section === "subscriptions" + ? "/assets/images/projects/shared/add-project.svg" + : ""; + return this.project?.imageAddress || currentImageAddress; } /** @@ -206,6 +215,15 @@ export class ProjectCardComponent implements OnInit { event.preventDefault(); } + /** + * Редирект на проеты при случае что подписки пустые + */ + redirectToProjects(): void { + this.router + .navigateByUrl(`/office/projects/all`) + .then(() => console.debug("Route change from ProjectsComponent")); + } + /** * Вычисление флага haveBadge */ diff --git a/projects/social_platform/src/app/ui/components/bar-new/bar.component.scss b/projects/social_platform/src/app/ui/components/bar-new/bar.component.scss index 4d803804c..fb4ab018f 100644 --- a/projects/social_platform/src/app/ui/components/bar-new/bar.component.scss +++ b/projects/social_platform/src/app/ui/components/bar-new/bar.component.scss @@ -2,7 +2,7 @@ .bar { display: block; - background-color: var(--gray); + background-color: var(--medium-grey-for-outline); border-radius: var(--rounded-xl); &__nav { diff --git a/projects/social_platform/src/app/ui/components/button/button.component.scss b/projects/social_platform/src/app/ui/components/button/button.component.scss index e931c7fe0..3739b14f9 100644 --- a/projects/social_platform/src/app/ui/components/button/button.component.scss +++ b/projects/social_platform/src/app/ui/components/button/button.component.scss @@ -115,7 +115,7 @@ &--small { width: 70px; - padding: 8px 24px; + padding: 4px 24px; &--icon { padding: 12px 24px; @@ -124,7 +124,7 @@ &--medium { width: 157px; - padding: 8px 0; + padding: 4px 0; &--icon { padding: 12px 60px; @@ -133,7 +133,7 @@ &--big { width: 100%; - padding: 8px 24px; + padding: 4px 24px; &--icon { width: 100%; diff --git a/projects/social_platform/src/app/utils/dashboardItemBuilder.ts b/projects/social_platform/src/app/utils/dashboardItemBuilder.ts new file mode 100644 index 000000000..fceaa87f8 --- /dev/null +++ b/projects/social_platform/src/app/utils/dashboardItemBuilder.ts @@ -0,0 +1,30 @@ +/** @format */ + +import { Project } from "@office/models/project.model"; + +export interface DashboardItem { + sectionName: string; + title: string; + iconName: string; + arrayItems: Project[]; +} + +export const dashboardItemBuilder = ( + amount: number, + sections: string[], + titles: string[], + icons: string[], + arrays: Project[][] +): DashboardItem[] => { + if (amount <= 0) return []; + + const minLength = Math.min(sections.length, titles.length, icons.length, arrays.length); + const actualAmount = Math.min(amount, minLength); + + return Array.from({ length: actualAmount }, (_, i) => ({ + sectionName: sections[i], + title: titles[i], + iconName: icons[i], + arrayItems: arrays[i], + })); +}; diff --git a/projects/social_platform/src/assets/images/projects/shared/empty-subscriptions.svg b/projects/social_platform/src/assets/images/projects/shared/empty-subscriptions.svg new file mode 100644 index 000000000..707bd9f59 --- /dev/null +++ b/projects/social_platform/src/assets/images/projects/shared/empty-subscriptions.svg @@ -0,0 +1,4 @@ + + + + From 33d00f8e2ed635859f9e1808bcc7b8a6c951fff6 Mon Sep 17 00:00:00 2001 From: Awakich Date: Thu, 18 Sep 2025 13:39:47 +0300 Subject: [PATCH 069/126] change styles for edit project & program rating-project page --- .../app/office/program/detail/detail/detail.component.scss | 2 +- .../src/app/office/projects/edit/edit.component.scss | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/projects/social_platform/src/app/office/program/detail/detail/detail.component.scss b/projects/social_platform/src/app/office/program/detail/detail/detail.component.scss index 23fa49ddd..47ef78b88 100644 --- a/projects/social_platform/src/app/office/program/detail/detail/detail.component.scss +++ b/projects/social_platform/src/app/office/program/detail/detail/detail.component.scss @@ -68,8 +68,8 @@ &__actions { display: grid; grid-template-columns: 2fr 2fr 1fr 2fr 2fr; - align-items: center; gap: 20px; + align-items: center; } &__presentation { diff --git a/projects/social_platform/src/app/office/projects/edit/edit.component.scss b/projects/social_platform/src/app/office/projects/edit/edit.component.scss index f7a92fd0e..0a887c056 100644 --- a/projects/social_platform/src/app/office/projects/edit/edit.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/edit.component.scss @@ -16,11 +16,11 @@ z-index: 100; display: flex; gap: 12%; - margin-top: 20px; - padding: 4px 0px; align-items: center; justify-content: space-evenly; width: 100%; + padding: 4px 0; + margin-top: 20px; background-color: var(--light-white); border-radius: var(--rounded-xxl); } From 052411cf8c6232f992b29a13c76848a0c3e8c110 Mon Sep 17 00:00:00 2001 From: Awakich Date: Thu, 18 Sep 2025 22:26:14 +0300 Subject: [PATCH 070/126] change rate-project page to list page --- .../detail/{detail => }/detail.component.html | 2 +- .../detail/{detail => }/detail.component.scss | 4 +- .../{detail => }/detail.component.spec.ts | 0 .../detail/{detail => }/detail.component.ts | 0 .../{detail => }/detail.resolver.spec.ts | 0 .../detail/{detail => }/detail.resolver.ts | 0 .../office/program/detail/detail.routes.ts | 8 +- .../program/detail/list/list.component.html | 18 +- .../program/detail/list/list.component.scss | 23 ++ .../program/detail/list/list.component.ts | 366 ++++++++++++------ .../rate-projects/list-all.resolver.spec.ts | 22 -- .../rate-projects/rate-project.routes.ts | 59 --- .../rate-projects.component.html | 36 -- .../rate-projects.component.scss | 153 -------- .../rate-projects.component.spec.ts | 27 -- .../rate-projects/rate-projects.component.ts | 238 ------------ 16 files changed, 299 insertions(+), 657 deletions(-) rename projects/social_platform/src/app/office/program/detail/{detail => }/detail.component.html (97%) rename projects/social_platform/src/app/office/program/detail/{detail => }/detail.component.scss (98%) rename projects/social_platform/src/app/office/program/detail/{detail => }/detail.component.spec.ts (100%) rename projects/social_platform/src/app/office/program/detail/{detail => }/detail.component.ts (100%) rename projects/social_platform/src/app/office/program/detail/{detail => }/detail.resolver.spec.ts (100%) rename projects/social_platform/src/app/office/program/detail/{detail => }/detail.resolver.ts (100%) delete mode 100644 projects/social_platform/src/app/office/program/detail/rate-projects/list-all.resolver.spec.ts delete mode 100644 projects/social_platform/src/app/office/program/detail/rate-projects/rate-project.routes.ts delete mode 100644 projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.html delete mode 100644 projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.scss delete mode 100644 projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.spec.ts delete mode 100644 projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.ts diff --git a/projects/social_platform/src/app/office/program/detail/detail/detail.component.html b/projects/social_platform/src/app/office/program/detail/detail.component.html similarity index 97% rename from projects/social_platform/src/app/office/program/detail/detail/detail.component.html rename to projects/social_platform/src/app/office/program/detail/detail.component.html index f99182d79..aaf17659c 100644 --- a/projects/social_platform/src/app/office/program/detail/detail/detail.component.html +++ b/projects/social_platform/src/app/office/program/detail/detail.component.html @@ -123,7 +123,7 @@ } @if (!program.isUserManager) { - +

-
diff --git a/projects/social_platform/src/app/office/program/detail/list/list.component.scss b/projects/social_platform/src/app/office/program/detail/list/list.component.scss index 3a19b51a3..6332ff14e 100644 --- a/projects/social_platform/src/app/office/program/detail/list/list.component.scss +++ b/projects/social_platform/src/app/office/program/detail/list/list.component.scss @@ -45,6 +45,12 @@ @include responsive.apply-desktop { grid-template-columns: repeat(4, 2fr); } + + &--rating { + grid-template-columns: 1fr; + row-gap: 20px; + margin-top: 0; + } } &__create { @@ -86,6 +92,23 @@ } } + &__controls { + display: flex; + flex-direction: column; + gap: 14px; + margin-top: 18px; + } + + &__tags { + display: flex; + gap: 12px; + align-items: center; + + p { + color: var(--grey-for-text); + } + } + &__bar { position: fixed; display: flex; diff --git a/projects/social_platform/src/app/office/program/detail/list/list.component.ts b/projects/social_platform/src/app/office/program/detail/list/list.component.ts index acd8b57c3..2a3216346 100644 --- a/projects/social_platform/src/app/office/program/detail/list/list.component.ts +++ b/projects/social_platform/src/app/office/program/detail/list/list.component.ts @@ -11,10 +11,12 @@ import { OnInit, Renderer2, ViewChild, + signal, } from "@angular/core"; import { catchError, concatMap, + debounceTime, distinctUntilChanged, fromEvent, map, @@ -28,18 +30,23 @@ import { import { ProjectsFilterComponent } from "@office/projects/projects-filter/projects-filter.component"; import Fuse from "fuse.js"; import { ActivatedRoute, Router, RouterModule } from "@angular/router"; -import { FormBuilder, FormGroup, ReactiveFormsModule } from "@angular/forms"; +import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms"; import { SearchComponent } from "@ui/components/search/search.component"; import { User } from "@auth/models/user.model"; import { Project } from "@office/models/project.model"; +import { ProjectRate } from "@office/program/models/project-rate"; import { ProjectCardComponent } from "@office/shared/project-card/project-card.component"; import { MemberCardComponent } from "@office/shared/member-card/member-card.component"; +import { RatingCardComponent } from "@office/program/shared/rating-card/rating-card.component"; import { ProgramService } from "@office/program/services/program.service"; +import { ProjectRatingService } from "@office/program/services/project-rating.service"; import { AuthService } from "@auth/services"; import { SubscriptionService } from "@office/services/subscription.service"; import { ApiPagination } from "@models/api-pagination.model"; import { HttpParams } from "@angular/common/http"; import { PartnerProgramFields } from "@office/models/partner-program-fields.model"; +import { CheckboxComponent } from "@ui/components"; +import { filterTags } from "projects/core/src/consts/filter-tags"; @Component({ selector: "app-list", @@ -53,13 +60,31 @@ import { PartnerProgramFields } from "@office/models/partner-program-fields.mode SearchComponent, ProjectCardComponent, MemberCardComponent, + RatingCardComponent, + CheckboxComponent, ], standalone: true, }) export class ProgramListComponent implements OnInit, OnDestroy, AfterViewInit { constructor() { + const isRatedByExpert = + this.route.snapshot.queryParams["is_rated_by_expert"] === "true" + ? true + : this.route.snapshot.queryParams["is_rated_by_expert"] === "false" + ? false + : null; + + const searchValue = + this.route.snapshot.queryParams["search"] || + this.route.snapshot.queryParams["name__contains"]; + const decodedSearchValue = searchValue ? decodeURIComponent(searchValue) : ""; + this.searchForm = this.fb.group({ - search: [""], + search: [decodedSearchValue], + }); + + this.filterForm = this.fb.group({ + filterTag: [isRatedByExpert, Validators.required], }); } @@ -72,6 +97,7 @@ export class ProgramListComponent implements OnInit, OnDestroy, AfterViewInit { private readonly router = inject(Router); private readonly cdref = inject(ChangeDetectorRef); private readonly programService = inject(ProgramService); + private readonly projectRatingService = inject(ProjectRatingService); private readonly authService = inject(AuthService); private readonly subscriptionService = inject(SubscriptionService); @@ -79,9 +105,10 @@ export class ProgramListComponent implements OnInit, OnDestroy, AfterViewInit { private availableFilters: PartnerProgramFields[] = []; searchForm: FormGroup; + filterForm: FormGroup; listTotalCount?: number; - listPage = 1; + listPage = 0; listTake = 20; perPage = 21; @@ -90,17 +117,21 @@ export class ProgramListComponent implements OnInit, OnDestroy, AfterViewInit { profile?: User; profileProjSubsIds?: number[]; - listType: "projects" | "members" = "projects"; + isRatedByExpert = signal(undefined); + searchValue = signal(""); + + listType: "projects" | "members" | "rating" = "projects"; + + readonly ratingOptionsList = filterTags; + isFilterOpen = false; subscriptions$: Subscription[] = []; ngOnInit(): void { - // Определяем тип списка this.route.data.subscribe(data => { this.listType = data["listType"]; }); - // Загружаем начальные данные из резолвера const routeData$ = this.route.data.pipe(map(r => r["data"])).subscribe(data => { this.listTotalCount = data.count; this.list = data.results; @@ -109,165 +140,264 @@ export class ProgramListComponent implements OnInit, OnDestroy, AfterViewInit { this.subscriptions$.push(routeData$); - // Форма поиска - const searchFormSearch$ = this.searchForm.get("search")?.valueChanges.subscribe(search => { - this.router - .navigate([], { - queryParams: { search }, - relativeTo: this.route, - queryParamsHandling: "merge", - }) - .then(() => console.debug("QueryParams changed from ProgramListComponent")); - }); - - searchFormSearch$ && this.subscriptions$.push(searchFormSearch$); + this.setupSearch(); if (this.listType === "projects") { - const profile$ = this.authService.profile + this.setupProfile(); + } + + this.setupFilters(); + + if (this.listType === "rating") { + this.setupRatingQueryParams(); + } + } + + ngAfterViewInit(): void { + const target = document.querySelector(".office__body"); + if (target) { + const scrollEvent$ = fromEvent(target, "scroll") .pipe( - switchMap(p => { - this.profile = p; - return this.subscriptionService.getSubscriptions(p.id).pipe( - map(resp => { - this.profileProjSubsIds = resp.results.map(sub => sub.id); - }) - ); - }) + debounceTime(this.listType === "rating" ? 200 : 500), + concatMap(() => this.onScroll()), + throttleTime(this.listType === "rating" ? 2000 : 500) ) - .subscribe(); + .subscribe(noop); - profile$ && this.subscriptions$.push(profile$); + this.subscriptions$.push(scrollEvent$); } + } + + ngOnDestroy(): void { + this.subscriptions$.forEach($ => $.unsubscribe()); + } + + private setupSearch(): void { + const searchFormSearch$ = this.searchForm + .get("search") + ?.valueChanges.pipe(debounceTime(300)) + .subscribe(search => { + this.router + .navigate([], { + queryParams: { [this.searchParamName]: search || null }, + relativeTo: this.route, + queryParamsHandling: "merge", + }) + .then(() => console.debug("QueryParams changed from ProgramListComponent")); + }); + + searchFormSearch$ && this.subscriptions$.push(searchFormSearch$); const querySearch$ = this.route.queryParams.pipe(map(q => q["search"])).subscribe(search => { - const searchKeys = this.listType === "projects" ? ["name"] : ["firstName", "lastName"]; + const searchKeys = + this.listType === "projects" || this.listType === "rating" + ? ["name"] + : ["firstName", "lastName"]; const fuse = new Fuse(this.list, { keys: searchKeys, }); this.searchedList = search ? fuse.search(search).map(el => el.item) : this.list; + this.cdref.detectChanges(); }); querySearch$ && this.subscriptions$.push(querySearch$); + } - // Фильтры (только для проектов) - if (this.listType === "projects") { - const filtersObservable$ = this.route.queryParams - .pipe( - distinctUntilChanged(), - concatMap(q => { - const reqQuery = this.buildFilterQuery(q); - const programId = this.route.parent?.snapshot.params["programId"]; - - if (JSON.stringify(reqQuery) !== JSON.stringify(this.previousReqQuery)) { - this.previousReqQuery = reqQuery; - - const hasFilters = - reqQuery && reqQuery["filters"] && Object.keys(reqQuery["filters"]).length > 0; - const params = new HttpParams({ fromObject: { offset: 0, limit: this.perPage } }); - - if (hasFilters) { - return this.programService - .createProgramFilters(programId, reqQuery["filters"]) - .pipe( - catchError(err => { - console.error("createFilters failed, fallback to getAllProjects()", err); - return this.programService.getAllProjects(programId, params); - }) - ); - } + private setupProfile(): void { + const profile$ = this.authService.profile + .pipe( + switchMap(p => { + this.profile = p; + return this.subscriptionService.getSubscriptions(p.id).pipe( + map(resp => { + this.profileProjSubsIds = resp.results.map(sub => sub.id); + }) + ); + }) + ) + .subscribe(); + + profile$ && this.subscriptions$.push(profile$); + } + + private setupFilters(): void { + if (this.listType !== "projects") return; + + const filtersObservable$ = this.route.queryParams + .pipe( + distinctUntilChanged(), + concatMap(q => { + const reqQuery = this.buildFilterQuery(q); + const programId = this.route.parent?.snapshot.params["programId"]; + + if (JSON.stringify(reqQuery) !== JSON.stringify(this.previousReqQuery)) { + this.previousReqQuery = reqQuery; - return this.programService.getAllProjects(programId, params).pipe( + const hasFilters = + reqQuery && reqQuery["filters"] && Object.keys(reqQuery["filters"]).length > 0; + const params = new HttpParams({ fromObject: { offset: 0, limit: this.perPage } }); + + if (hasFilters) { + return this.programService.createProgramFilters(programId, reqQuery["filters"]).pipe( catchError(err => { - console.error("getAllProjects failed", err); + console.error("createFilters failed, fallback to getAllProjects()", err); return this.programService.getAllProjects(programId, params); }) ); } - return of(null); - }) - ) - .subscribe(result => { - if (result && typeof result !== "number") { - this.list = result.results; - this.searchedList = result.results; - this.listTotalCount = result.count; - this.cdref.detectChanges(); + return this.programService.getAllProjects(programId, params).pipe( + catchError(err => { + console.error("getAllProjects failed", err); + return this.programService.getAllProjects(programId, params); + }) + ); } - }); - this.subscriptions$.push(filtersObservable$); - } + return of(null); + }) + ) + .subscribe(result => { + if (result && typeof result !== "number") { + this.list = result.results; + this.searchedList = result.results; + this.listTotalCount = result.count; + this.listPage = 0; + this.cdref.detectChanges(); + } + }); + + this.subscriptions$.push(filtersObservable$); } - ngAfterViewInit(): void { - const target = document.querySelector(".office__body"); - if (target) { - const scrollEvent$ = fromEvent(target, "scroll") - .pipe( - throttleTime(500), - concatMap(() => this.onScroll()) - ) - .subscribe(noop); - this.subscriptions$.push(scrollEvent$); - } + private setupRatingQueryParams(): void { + const queryParams$ = this.route.queryParams + .pipe( + debounceTime(200), + tap(params => { + const isRatedByExpert = + params["is_rated_by_expert"] === "true" + ? true + : params["is_rated_by_expert"] === "false" + ? false + : undefined; + const searchValue = params["name__contains"] || ""; + + this.isRatedByExpert.set(isRatedByExpert); + this.searchValue.set(searchValue); + }), + switchMap(() => { + this.listPage = 0; + return this.onFetch(); + }) + ) + .subscribe(); + + this.subscriptions$.push(queryParams$); } - ngOnDestroy(): void { - this.subscriptions$.forEach($ => $.unsubscribe()); + // Методы фильтрации + setValue(event: Event): void { + event.stopPropagation(); + this.filterForm.get("filterTag")?.setValue(!this.filterForm.get("filterTag")?.value); + + this.router.navigate([], { + queryParams: { is_rated_by_expert: this.filterForm.get("filterTag")?.value }, + relativeTo: this.route, + queryParamsHandling: "merge", + }); } - // Логика бесконечной прокрутки + // Универсальный метод скролла private onScroll() { if (this.listTotalCount && this.list.length >= this.listTotalCount) return of({}); const target = document.querySelector(".office__body"); - if (!target || !this.listRoot) return of({}); + if (!target || (this.listType !== "rating" && !this.listRoot)) return of({}); - const diff = - target.scrollTop - - this.listRoot.nativeElement.getBoundingClientRect().height + - window.innerHeight; - const threshold = this.listType === "projects" ? -200 : 0; + let shouldFetch = false; - if (diff > threshold) { + if (this.listType === "rating") { + // Логика для rating + const scrollBottom = target.scrollHeight - target.scrollTop - target.clientHeight; + shouldFetch = scrollBottom <= 0; + } else { + // Логика для projects и members + const diff = + target.scrollTop - + this.listRoot!.nativeElement.getBoundingClientRect().height + + window.innerHeight; + const threshold = this.listType === "projects" ? -200 : 0; + shouldFetch = diff > threshold; + } + + if (shouldFetch) { + this.listPage++; return this.onFetch(); } return of({}); } - // Загрузка следующей порции данных + // Универсальный метод загрузки данных private onFetch() { const programId = this.route.parent?.snapshot.params["programId"]; + const offset = this.listPage * this.itemsPerPage; + + switch (this.listType) { + case "projects": + return this.programService + .getAllProjects( + programId, + new HttpParams({ fromObject: { offset, limit: this.itemsPerPage } }) + ) + .pipe( + tap((projects: ApiPagination) => { + this.listTotalCount = projects.count; + if (this.listPage === 0) { + this.list = projects.results; + } else { + this.list = [...this.list, ...projects.results]; + } + this.searchedList = this.list; + this.cdref.detectChanges(); + }) + ); - if (this.listType === "projects") { - const offset = this.listPage * this.perPage; - const params = new HttpParams({ fromObject: { offset, limit: this.perPage } }); - - return this.programService.getAllProjects(programId, params).pipe( - tap((projects: ApiPagination) => { - this.listTotalCount = projects.count; - this.list = [...this.list, ...projects.results]; - this.searchedList = this.list; - this.listPage++; - this.cdref.detectChanges(); - }) - ); - } else { - return this.programService - .getAllMembers(programId, this.listPage * this.listTake, this.listTake) - .pipe( + case "members": + return this.programService.getAllMembers(programId, offset, this.itemsPerPage).pipe( tap((members: ApiPagination) => { this.listTotalCount = members.count; - this.list = [...this.list, ...members.results]; + if (this.listPage === 0) { + this.list = members.results; + } else { + this.list = [...this.list, ...members.results]; + } this.searchedList = this.list; - this.listPage++; this.cdref.detectChanges(); }) ); + + case "rating": + return this.projectRatingService + .getAll(programId, offset, this.itemsPerPage, this.isRatedByExpert(), this.searchValue()) + .pipe( + tap(({ count, results }) => { + this.listTotalCount = count; + if (this.listPage === 0) { + this.list = results; + } else { + this.list = [...this.list, ...results]; + } + this.searchedList = this.list; + this.cdref.detectChanges(); + }) + ); + + default: + return of({}); } } @@ -295,13 +425,11 @@ export class ProgramListComponent implements OnInit, OnDestroy, AfterViewInit { return { filters }; } - // Обработка загруженных фильтров onFiltersLoaded(filters: PartnerProgramFields[]): void { this.availableFilters = filters; } // Swipe логика для мобильных устройств - isFilterOpen = false; private swipeStartY = 0; private swipeThreshold = 50; private isSwiping = false; @@ -343,4 +471,16 @@ export class ProgramListComponent implements OnInit, OnDestroy, AfterViewInit { closeFilter(): void { this.isFilterOpen = false; } + + private get itemsPerPage(): number { + return this.listType === "rating" + ? 8 + : this.listType === "projects" + ? this.perPage + : this.listTake; + } + + private get searchParamName(): string { + return this.listType === "rating" ? "name__contains" : "search"; + } } diff --git a/projects/social_platform/src/app/office/program/detail/rate-projects/list-all.resolver.spec.ts b/projects/social_platform/src/app/office/program/detail/rate-projects/list-all.resolver.spec.ts deleted file mode 100644 index 880c3dc7b..000000000 --- a/projects/social_platform/src/app/office/program/detail/rate-projects/list-all.resolver.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** @format */ - -import { TestBed } from "@angular/core/testing"; - -import { ListAllResolver } from "../list/list-all.resolver"; -import { HttpClientTestingModule } from "@angular/common/http/testing"; -import { ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router"; - -describe("ListAllResolver", () => { - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - }); - }); - - it("should be created", () => { - const result = TestBed.runInInjectionContext(() => - ListAllResolver({} as ActivatedRouteSnapshot, {} as RouterStateSnapshot) - ); - expect(result).toBeTruthy(); - }); -}); diff --git a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-project.routes.ts b/projects/social_platform/src/app/office/program/detail/rate-projects/rate-project.routes.ts deleted file mode 100644 index 28129b7f5..000000000 --- a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-project.routes.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** @format */ - -import { Routes } from "@angular/router"; -import { RateProjectsComponent } from "@office/program/detail/rate-projects/rate-projects.component"; -import { ListComponent } from "./list/list.component"; -import { ListAllResolver } from "../list/list-all.resolver"; -// import { ListRatedResolver } from "./list/list-rated.resolver"; - -/** - * Маршруты для модуля оценки проектов программы - * - * Определяет структуру навигации для экспертной оценки проектов: - * - Главная страница с поиском и фильтрами - * - Список всех проектов для оценки - * - * Структура маршрутов: - * - "" - корневой компонент RateProjectsComponent - * - "" - редирект на "all" - * - "all" - список всех проектов с резолвером данных - * - * Закомментированный маршрут: - * - "rated" - предположительно для уже оцененных проектов - * - * Резолверы: - * - ListAllResolver - предзагружает проекты для оценки - * - * Компоненты: - * - RateProjectsComponent - контейнер с поиском и навигацией - * - ListComponent - отображение списка проектов - * - * @returns {Routes} Конфигурация маршрутов для оценки проектов - */ -export const RATE_PROJECTS_ROUTES: Routes = [ - { - path: "", - component: RateProjectsComponent, - children: [ - { - path: "", - redirectTo: "all", - pathMatch: "full", - }, - { - path: "all", - component: ListComponent, - resolve: { - data: ListAllResolver, - }, - }, - // { - // path: "rated", - // component: ListComponent, - // resolve: { - // data: ListRatedResolver, - // }, - // }, - ], - }, -]; diff --git a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.html b/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.html deleted file mode 100644 index 8e4872807..000000000 --- a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.html +++ /dev/null @@ -1,36 +0,0 @@ - - -
-
- - -
- фильтр - -
- -
    - @for (p of projects; track p.id) { - - } -
-
- -
-
-
-

фильтр

-
- -
-
-
-
-
diff --git a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.scss b/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.scss deleted file mode 100644 index a5b4b4d47..000000000 --- a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.scss +++ /dev/null @@ -1,153 +0,0 @@ -@use "styles/responsive"; -@use "styles/typography"; - -.page { - display: grid; - grid-template-columns: 8fr 2fr; - gap: 20px; - padding-bottom: 100px; - - &__search { - flex-grow: 1; - } - - &__outlet { - display: flex; - flex-direction: column; - flex-grow: 1; - gap: 20px; - width: 100%; - margin-top: 10px; - } - - &__filter { - display: none; - - &--open { - display: block; - } - - @include responsive.apply-desktop { - display: block; - margin-left: 16px; - } - } - - &__list { - display: grid; - flex-grow: 1; - grid-template-columns: 1fr; - column-gap: 20px; - align-items: flex-start; - } - - &__create { - display: flex; - flex-direction: column; - gap: 20px; - align-items: center; - margin-top: 20px; - } -} - -.filter { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 10; - - @include responsive.apply-desktop { - position: static; - } - - &__controls { - display: flex; - flex-direction: column; - gap: 14px; - margin-top: 18px; - } - - &__tags { - display: flex; - gap: 12px; - align-items: center; - - p { - color: var(--grey-for-text); - } - } - - &__overlay { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background-color: black; - opacity: 0.3; - - @include responsive.apply-desktop { - display: none; - } - } - - &__bar { - position: fixed; - display: flex; - width: 100%; - height: 25px; - touch-action: none; - - @include responsive.apply-desktop { - display: none; - } - - &::after { - display: block; - width: 85px; - height: 5px; - margin: auto; - content: ""; - background-color: var(--gray); - border-radius: var(--rounded-lg); - transition: transform 0.2s; - } - } - - &__body { - position: absolute; - right: 0; - bottom: 0; - left: 0; - z-index: 10; - max-height: 72vh; - overflow-y: auto; - background-color: var(--white); - border-radius: var(--rounded-lg); - transform: translateY(0%); - - @include responsive.apply-desktop { - position: static; - max-height: unset; - transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); - } - } -} - -.filter-toggle { - position: relative; - display: flex; - align-items: center; - justify-content: space-between; - padding: 15px 10px; - cursor: pointer; - background-color: var(--white); - border: 1px solid var(--medium-medium-grey-for-outline); - border-radius: var(--rounded-xl); - - @include responsive.apply-desktop { - display: none; - } -} diff --git a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.spec.ts b/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.spec.ts deleted file mode 100644 index 1d7ee29d7..000000000 --- a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** @format */ - -import { ComponentFixture, TestBed } from "@angular/core/testing"; - -import { RateProjectsComponent } from "./rate-projects.component"; -import { RouterTestingModule } from "@angular/router/testing"; - -describe("RateProjectsComponent", () => { - let component: RateProjectsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [RouterTestingModule, RateProjectsComponent], - }).compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(RateProjectsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it("should create", () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.ts b/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.ts deleted file mode 100644 index 3cc9124c2..000000000 --- a/projects/social_platform/src/app/office/program/detail/rate-projects/rate-projects.component.ts +++ /dev/null @@ -1,238 +0,0 @@ -/** @format */ - -import { Component, OnDestroy, OnInit, signal } from "@angular/core"; -import { NavService } from "@services/nav.service"; -import { ActivatedRoute, Router } from "@angular/router"; -import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms"; -import { - concatMap, - debounceTime, - fromEvent, - map, - of, - Subscription, - switchMap, - tap, - throttleTime, -} from "rxjs"; -import { ProjectRate } from "@office/program/models/project-rate"; -import { ProjectRatingService } from "@office/program/services/project-rating.service"; -import { CommonModule } from "@angular/common"; -import { RatingCardComponent } from "@office/program/shared/rating-card/rating-card.component"; -import { CheckboxComponent, ButtonComponent, SelectComponent } from "@ui/components"; -import { filterTags } from "projects/core/src/consts/filter-tags"; -import { SearchComponent } from "@ui/components/search/search.component"; - -/** - * Компонент страницы оценки проектов программы - * - * Основной компонент для экспертной оценки проектов в рамках программы. - * Предоставляет интерфейс поиска и фильтрации проектов для оценки. - * - * Функциональность: - * - Поиск проектов по названию - * - Навигационная панель - * - Router outlet для дочерних компонентов (список проектов) - * - Обработка URL параметров поиска - * - * Принимает: - * @param {NavService} navService - Сервис навигации для установки заголовка - * @param {Router} router - Для навигации и изменения URL параметров - * @param {ActivatedRoute} route - Для получения параметров маршрута - * @param {FormBuilder} fb - Для создания реактивных форм - * - * Формы: - * @property {FormGroup} searchForm - Форма поиска проектов - * - * Состояние: - * @property {number} programId - ID текущей программы - * @property {Subscription[]} subscriptions$ - Массив подписок для очистки - * - * Методы: - * @method onSearchClick() - Обработчик поиска, обновляет URL параметры - * @method onClickOutside() - Закрывает выпадающие меню при клике вне - * - * Возвращает: - * HTML шаблон с поиском и router-outlet для списка проектов - */ -@Component({ - selector: "app-rate-projects", - templateUrl: "./rate-projects.component.html", - styleUrl: "./rate-projects.component.scss", - standalone: true, - imports: [ - CommonModule, - ReactiveFormsModule, - RatingCardComponent, - CheckboxComponent, - SearchComponent, - ], -}) -export class RateProjectsComponent implements OnInit, OnDestroy { - constructor( - private readonly navService: NavService, - private readonly projectRatingService: ProjectRatingService, - private readonly router: Router, - private readonly route: ActivatedRoute, - private readonly fb: FormBuilder - ) { - const isRatedByExpert = - this.route.snapshot.queryParams["is_rated_by_expert"] === "true" - ? true - : this.route.snapshot.queryParams["is_rated_by_expert"] === "false" - ? false - : null; - - const searchValue = this.route.snapshot.queryParams["name__contains"]; - const decodedSearchValue = searchValue ? decodeURIComponent(searchValue) : ""; - - this.searchForm = this.fb.group({ - search: [decodedSearchValue], - }); - - this.filterForm = this.fb.group({ - filterTag: [isRatedByExpert, Validators.required], - }); - } - - readonly ratingOptionsList = filterTags; - - searchForm: FormGroup; - filterForm: FormGroup; - - subscriptions$: Subscription[] = []; - programId?: number; - - isFilterOpen = false; - - isListOfAll = this.router.url.includes("/all"); - isRatedByExpert = signal(undefined); - searchValue = signal(""); - - projects: ProjectRate[] = []; - initialProjects: ProjectRate[] = []; - - totalProjCount = 0; - fetchLimit = 8; - fetchPage = 0; - - ngOnInit(): void { - this.navService.setNavTitle("Профиль программы"); - this.programId = this.route.snapshot.params["programId"]; - - const initProjects$ = this.route.data - .pipe( - map(r => r["data"]), - map(r => ({ projects: r["results"], count: r["count"] })) - ) - .subscribe(({ projects, count }) => { - this.initialProjects = projects; - this.projects = projects; - this.totalProjCount = count; - }); - - const queryParams$ = this.route.queryParams - .pipe( - debounceTime(200), - tap(params => { - const isRatedByExpert = - params["is_rated_by_expert"] === "true" - ? true - : params["is_rated_by_expert"] === "false" - ? false - : undefined; - const searchValue = params["name__contains"]; - - this.isRatedByExpert.set(isRatedByExpert); - this.searchValue.set(searchValue); - }), - switchMap(() => this.onFetch(this.fetchPage * this.fetchLimit, this.fetchLimit)) - ) - .subscribe((params: any) => { - this.projects = params.results; - const searchValue = params["name__contains"]; - this.searchForm.get("search")?.setValue(searchValue, { emitEvent: false }); - }); - - this.subscriptions$.push(initProjects$, queryParams$); - - this.subscriptions$.push(queryParams$); - } - - ngAfterViewInit() { - const target = document.querySelector(".office__body"); - if (target) { - const scrollEvents$ = fromEvent(target, "scroll") - .pipe( - debounceTime(200), - concatMap(() => this.onScroll()), - throttleTime(2000) - ) - .subscribe(); - - this.subscriptions$.push(scrollEvents$); - } - } - - ngOnDestroy(): void { - this.subscriptions$.forEach($ => $?.unsubscribe()); - } - - setValue(event: Event) { - event.stopPropagation(); - this.filterForm.get("filterTag")?.setValue(!this.filterForm.get("filterTag")?.value); - - this.router.navigate([], { - queryParams: { is_rated_by_expert: this.filterForm.get("filterTag")?.value }, - relativeTo: this.route, - queryParamsHandling: "merge", - }); - } - - onSearchClick() { - const searchValue = this.searchForm.get("search")?.value; - - this.router.navigate([], { - queryParams: { name__contains: searchValue }, - relativeTo: this.route, - queryParamsHandling: "merge", - }); - - this.searchForm.get("search")?.reset(); - } - - onScroll() { - if (this.projects.length >= this.totalProjCount) { - return of({}); - } - - const target = document.querySelector(".office__body"); - if (!target) return of({}); - - const scrollBottom = target.scrollHeight - target.scrollTop - target.clientHeight; - - if (scrollBottom > 0) return of({}); - - this.fetchPage += 1; - - return this.onFetch(this.fetchPage * this.fetchLimit, this.fetchLimit); - } - - onFetch(offset: number, limit: number) { - const programId = this.route.parent?.snapshot.params["programId"]; - const observable = this.projectRatingService.getAll( - programId, - offset, - limit, - this.isRatedByExpert(), - this.searchValue() - ); - - return observable.pipe( - tap(({ count, results }) => { - this.totalProjCount = count; - this.projects = results; - }) - ); - } -} From 716c21ccf379386111b2c442eb672318d98eda1a Mon Sep 17 00:00:00 2001 From: Awakich Date: Fri, 19 Sep 2025 12:49:03 +0300 Subject: [PATCH 071/126] unify project-card & member-card to info card --- .../app/office/members/members.component.html | 2 +- .../app/office/members/members.component.ts | 4 +- .../detail/projects/projects.component.html | 12 +- .../detail/projects/projects.component.ts | 4 +- .../program/detail/list/list.component.html | 15 +- .../program/detail/list/list.component.ts | 20 +- .../app/office/program/program.component.ts | 2 - .../dashboardItem.component.html | 10 +- .../dashboardItem/dashboardItem.component.ts | 4 +- .../office/projects/list/list.component.html | 8 +- .../office/projects/list/list.component.ts | 4 +- .../office/projects/projects.component.html | 2 +- .../app/office/projects/projects.component.ts | 4 +- .../info-card.component.html} | 62 ++++-- .../info-card.component.scss} | 10 + .../info-card.component.spec.ts} | 12 +- .../info-card.component.ts} | 34 ++-- .../member-card/member-card.component.html | 49 ----- .../member-card/member-card.component.scss | 177 ------------------ .../member-card/member-card.component.spec.ts | 39 ---- .../member-card/member-card.component.ts | 54 ------ .../skills-basket.component.html | 5 +- .../skills-basket.component.scss | 9 +- .../vacancy-card/vacancy-card.component.html | 4 +- 24 files changed, 137 insertions(+), 409 deletions(-) rename projects/social_platform/src/app/office/shared/{project-card/project-card.component.html => info-card/info-card.component.html} (72%) rename projects/social_platform/src/app/office/shared/{project-card/project-card.component.scss => info-card/info-card.component.scss} (96%) rename projects/social_platform/src/app/office/shared/{project-card/project-card.component.spec.ts => info-card/info-card.component.spec.ts} (64%) rename projects/social_platform/src/app/office/shared/{project-card/project-card.component.ts => info-card/info-card.component.ts} (85%) delete mode 100644 projects/social_platform/src/app/office/shared/member-card/member-card.component.html delete mode 100644 projects/social_platform/src/app/office/shared/member-card/member-card.component.scss delete mode 100644 projects/social_platform/src/app/office/shared/member-card/member-card.component.spec.ts delete mode 100644 projects/social_platform/src/app/office/shared/member-card/member-card.component.ts diff --git a/projects/social_platform/src/app/office/members/members.component.html b/projects/social_platform/src/app/office/members/members.component.html index 6b973c71a..c1b902a34 100644 --- a/projects/social_platform/src/app/office/members/members.component.html +++ b/projects/social_platform/src/app/office/members/members.component.html @@ -14,7 +14,7 @@

Участники

@for (member of members; track member.id) {
  • - +
  • } diff --git a/projects/social_platform/src/app/office/members/members.component.ts b/projects/social_platform/src/app/office/members/members.component.ts index 511c8c21c..e47024dc1 100644 --- a/projects/social_platform/src/app/office/members/members.component.ts +++ b/projects/social_platform/src/app/office/members/members.component.ts @@ -39,11 +39,11 @@ import { } from "@angular/forms"; import { containerSm } from "@utils/responsive"; import { MemberService } from "@services/member.service"; -import { MemberCardComponent } from "../shared/member-card/member-card.component"; import { CommonModule } from "@angular/common"; import { SearchComponent } from "@ui/components/search/search.component"; import { MembersFiltersComponent } from "./filters/members-filters.component"; import { ApiPagination } from "@models/api-pagination.model"; +import { InfoCardComponent } from "@office/shared/info-card/info-card.component"; /** * Компонент для отображения списка участников с возможностью поиска и фильтрации @@ -68,8 +68,8 @@ import { ApiPagination } from "@models/api-pagination.model"; SearchComponent, CommonModule, RouterLink, - MemberCardComponent, MembersFiltersComponent, + InfoCardComponent, ], }) export class MembersComponent implements OnInit, OnDestroy, AfterViewInit { diff --git a/projects/social_platform/src/app/office/profile/detail/projects/projects.component.html b/projects/social_platform/src/app/office/profile/detail/projects/projects.component.html index baf44d358..25897cfdf 100644 --- a/projects/social_platform/src/app/office/profile/detail/projects/projects.component.html +++ b/projects/social_platform/src/app/office/profile/detail/projects/projects.component.html @@ -13,10 +13,10 @@

    }}

      - @for (p of user.projects; track p.id) { + @for (project of user.projects; track project.id) {
    • - - + +
    • } @@ -32,10 +32,10 @@

      }}

        - @for (p of subs; track p.id) { + @for (project of subs; track project.id) {
      • - - + +
      • } diff --git a/projects/social_platform/src/app/office/profile/detail/projects/projects.component.ts b/projects/social_platform/src/app/office/profile/detail/projects/projects.component.ts index eb499837f..84da5b47a 100644 --- a/projects/social_platform/src/app/office/profile/detail/projects/projects.component.ts +++ b/projects/social_platform/src/app/office/profile/detail/projects/projects.component.ts @@ -5,9 +5,9 @@ import { ActivatedRoute, RouterLink } from "@angular/router"; import { User } from "@auth/models/user.model"; import { AuthService } from "@auth/services"; import { map, Observable } from "rxjs"; -import { ProjectCardComponent } from "@office/shared/project-card/project-card.component"; import { AsyncPipe } from "@angular/common"; import { Project } from "@office/models/project.model"; +import { InfoCardComponent } from "@office/shared/info-card/info-card.component"; /** * Компонент для отображения проектов пользователя @@ -29,7 +29,7 @@ import { Project } from "@office/models/project.model"; templateUrl: "./projects.component.html", styleUrl: "./projects.component.scss", standalone: true, - imports: [RouterLink, ProjectCardComponent, AsyncPipe], + imports: [RouterLink, AsyncPipe, InfoCardComponent], }) export class ProfileProjectsComponent implements OnInit { constructor(private readonly route: ActivatedRoute, public readonly authService: AuthService) {} diff --git a/projects/social_platform/src/app/office/program/detail/list/list.component.html b/projects/social_platform/src/app/office/program/detail/list/list.component.html index a6c99472e..5267e4f67 100644 --- a/projects/social_platform/src/app/office/program/detail/list/list.component.html +++ b/projects/social_platform/src/app/office/program/detail/list/list.component.html @@ -16,17 +16,14 @@
          @for (listItem of searchedList; track listItem.id) {
        • - @if (listType === 'projects') { - - + - - } @else if (listType === 'members') { - - + [info]="listItem" + > } @else { diff --git a/projects/social_platform/src/app/office/program/detail/list/list.component.ts b/projects/social_platform/src/app/office/program/detail/list/list.component.ts index 2a3216346..b84182d84 100644 --- a/projects/social_platform/src/app/office/program/detail/list/list.component.ts +++ b/projects/social_platform/src/app/office/program/detail/list/list.component.ts @@ -34,9 +34,6 @@ import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from "@angula import { SearchComponent } from "@ui/components/search/search.component"; import { User } from "@auth/models/user.model"; import { Project } from "@office/models/project.model"; -import { ProjectRate } from "@office/program/models/project-rate"; -import { ProjectCardComponent } from "@office/shared/project-card/project-card.component"; -import { MemberCardComponent } from "@office/shared/member-card/member-card.component"; import { RatingCardComponent } from "@office/program/shared/rating-card/rating-card.component"; import { ProgramService } from "@office/program/services/program.service"; import { ProjectRatingService } from "@office/program/services/project-rating.service"; @@ -47,6 +44,7 @@ import { HttpParams } from "@angular/common/http"; import { PartnerProgramFields } from "@office/models/partner-program-fields.model"; import { CheckboxComponent } from "@ui/components"; import { filterTags } from "projects/core/src/consts/filter-tags"; +import { InfoCardComponent } from "@office/shared/info-card/info-card.component"; @Component({ selector: "app-list", @@ -58,10 +56,9 @@ import { filterTags } from "projects/core/src/consts/filter-tags"; RouterModule, ProjectsFilterComponent, SearchComponent, - ProjectCardComponent, - MemberCardComponent, RatingCardComponent, CheckboxComponent, + InfoCardComponent, ], standalone: true, }) @@ -127,6 +124,19 @@ export class ProgramListComponent implements OnInit, OnDestroy, AfterViewInit { subscriptions$: Subscription[] = []; + routerLink(linkId: number): string { + switch (this.listType) { + case "projects": + return `/office/projects/${linkId}`; + + case "members": + return `/office/profile/${linkId}`; + + default: + return ""; + } + } + ngOnInit(): void { this.route.data.subscribe(data => { this.listType = data["listType"]; diff --git a/projects/social_platform/src/app/office/program/program.component.ts b/projects/social_platform/src/app/office/program/program.component.ts index 482501c31..6c8b51465 100644 --- a/projects/social_platform/src/app/office/program/program.component.ts +++ b/projects/social_platform/src/app/office/program/program.component.ts @@ -68,7 +68,6 @@ export class ProgramComponent implements OnInit, OnDestroy { const routeUrl$ = this.router.events.subscribe(event => { if (event instanceof NavigationEnd) { - this.isMy = location.href.includes("/my"); this.isAll = location.href.includes("/all"); } }); @@ -82,6 +81,5 @@ export class ProgramComponent implements OnInit, OnDestroy { searchForm: FormGroup; subscriptions$: Subscription[] = []; - isMy = location.href.includes("/my"); isAll = location.href.includes("/all"); } diff --git a/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.html b/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.html index 02f5ede33..d1bdd3a74 100644 --- a/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.html +++ b/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.html @@ -10,17 +10,17 @@ } @else {
          - + >
          } diff --git a/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.ts b/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.ts index a201c9dc7..ed8acd83f 100644 --- a/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.ts +++ b/projects/social_platform/src/app/office/projects/dashboard/shared/dashboardItem/dashboardItem.component.ts @@ -3,17 +3,17 @@ import { CommonModule } from "@angular/common"; import { Component, inject, Input, OnInit } from "@angular/core"; import { Project } from "@office/models/project.model"; -import { ProjectCardComponent } from "@office/shared/project-card/project-card.component"; import { IconComponent } from "@uilib"; import { ProjectsService } from "@office/projects/services/projects.service"; import { RouterLink } from "@angular/router"; +import { InfoCardComponent } from "@office/shared/info-card/info-card.component"; @Component({ selector: "app-dashboard-item", templateUrl: "./dashboardItem.component.html", styleUrl: "./dashboardItem.component.scss", standalone: true, - imports: [ProjectCardComponent, CommonModule, IconComponent, RouterLink], + imports: [CommonModule, IconComponent, RouterLink, InfoCardComponent], }) export class DashboardItemComponent implements OnInit { @Input() title!: string; diff --git a/projects/social_platform/src/app/office/projects/list/list.component.html b/projects/social_platform/src/app/office/projects/list/list.component.html index 85a36a03f..5cfd4f614 100644 --- a/projects/social_platform/src/app/office/projects/list/list.component.html +++ b/projects/social_platform/src/app/office/projects/list/list.component.html @@ -11,14 +11,14 @@ @for (project of projects; track project.id) {
        • - + >
        • } diff --git a/projects/social_platform/src/app/office/projects/list/list.component.ts b/projects/social_platform/src/app/office/projects/list/list.component.ts index 3132e5e61..d588ca020 100644 --- a/projects/social_platform/src/app/office/projects/list/list.component.ts +++ b/projects/social_platform/src/app/office/projects/list/list.component.ts @@ -31,7 +31,7 @@ import { NavService } from "@services/nav.service"; import { ProjectService } from "@services/project.service"; import { HttpParams } from "@angular/common/http"; import { ApiPagination } from "@models/api-pagination.model"; -import { ProjectCardComponent } from "../../shared/project-card/project-card.component"; +import { InfoCardComponent } from "../../shared/info-card/info-card.component"; import { IconComponent } from "@ui/components"; import { SubscriptionService } from "@office/services/subscription.service"; import { inviteToProjectMapper } from "@utils/inviteToProjectMapper"; @@ -87,7 +87,7 @@ import { inviteToProjectMapper } from "@utils/inviteToProjectMapper"; templateUrl: "./list.component.html", styleUrl: "./list.component.scss", standalone: true, - imports: [IconComponent, RouterLink, ProjectCardComponent], + imports: [IconComponent, RouterLink, InfoCardComponent], }) export class ProjectsListComponent implements OnInit, AfterViewInit, OnDestroy { private readonly renderer = inject(Renderer2); diff --git a/projects/social_platform/src/app/office/projects/projects.component.html b/projects/social_platform/src/app/office/projects/projects.component.html index 6b1a35a09..5f2678702 100644 --- a/projects/social_platform/src/app/office/projects/projects.component.html +++ b/projects/social_platform/src/app/office/projects/projects.component.html @@ -70,7 +70,7 @@ @if (myInvites.length) {
            @for (invite of myInvites; track invite.id) { - + }
          } @else { diff --git a/projects/social_platform/src/app/office/projects/projects.component.ts b/projects/social_platform/src/app/office/projects/projects.component.ts index 28befad51..801509c93 100644 --- a/projects/social_platform/src/app/office/projects/projects.component.ts +++ b/projects/social_platform/src/app/office/projects/projects.component.ts @@ -11,10 +11,10 @@ import { BarNewComponent } from "@ui/components/bar-new/bar.component"; import { BackComponent } from "@uilib"; import { SoonCardComponent } from "@office/shared/soon-card/soon-card.component"; import { ProjectsFilterComponent } from "./projects-filter/projects-filter.component"; -import { ProjectCardComponent } from "@office/shared/project-card/project-card.component"; import { Project } from "@office/models/project.model"; import { inviteToProjectMapper } from "@utils/inviteToProjectMapper"; import { ProjectsService } from "./services/projects.service"; +import { InfoCardComponent } from "@office/shared/info-card/info-card.component"; /** * Главный компонент модуля проектов @@ -43,7 +43,7 @@ import { ProjectsService } from "./services/projects.service"; BackComponent, SoonCardComponent, ProjectsFilterComponent, - ProjectCardComponent, + InfoCardComponent, ], }) export class ProjectsComponent implements OnInit, OnDestroy { diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.html b/projects/social_platform/src/app/office/shared/info-card/info-card.component.html similarity index 72% rename from projects/social_platform/src/app/office/shared/project-card/project-card.component.html rename to projects/social_platform/src/app/office/shared/info-card/info-card.component.html index 213def7dc..7f711d553 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.html +++ b/projects/social_platform/src/app/office/shared/info-card/info-card.component.html @@ -67,7 +67,7 @@
    -

    {{ project?.name }}

    +

    {{ info?.name }}

    arrow @if (section === 'subscriptions') {

    перейти к проектам

    @@ -77,50 +77,74 @@
    -

    {{ project?.name }}

    + @if (type === 'projects') { +

    {{ info?.name }}

    @if (industryService.industries | async; as industries) {

    - @if (industryService.getIndustry(industries, project?.industry!); as industry) { - {{ industry?.name || "Отрасль не указана" }} + @if (industryService.getIndustry(industries, info?.industry!); as industry) { + {{ industry?.name }} }

    - } + } } @else { +

    {{ info?.firstName }} {{ info?.lastName }}

    + @if (info?.speciality) { +

    + {{ info?.speciality }} + @if (info?.speciality && info?.birthday) { • } @if (info?.birthday) { + {{ info?.birthday! | yearsFromBirthday }} + } +

    + } }
    @if (type === 'invite') {

    вас приглашает

    -

    {{ project?.shortDescription }}

    - } @else { -

    {{ project?.shortDescription }}

    - } +

    {{ info?.shortDescription }}

    + } @else if (type === 'projects') { +

    {{ info?.shortDescription }}

    + } @else { @if (info?.skills && info?.skills?.length) { +
      + @for ( skill of info?.skills?.slice(0, 3); track skill.id ) { +
    • + {{ + skill.name.length > 10 ? skill.name.slice(0, 10) + " " + "..." : skill.name + }} +
    • + } +
    + } }
    -
    - @if (type === 'project') { - +
    + @if (type === 'projects') { + проект + } @else if (type === 'members') { + + профиль + } @else {
    принять отклонить @@ -133,13 +157,13 @@

    Вы действительно хотите отписаться от проекта?

    - +
    Отписаться diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss b/projects/social_platform/src/app/office/shared/info-card/info-card.component.scss similarity index 96% rename from projects/social_platform/src/app/office/shared/project-card/project-card.component.scss rename to projects/social_platform/src/app/office/shared/info-card/info-card.component.scss index 7e6ed92de..f44da27db 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.scss +++ b/projects/social_platform/src/app/office/shared/info-card/info-card.component.scss @@ -89,6 +89,10 @@ white-space: nowrap; } + &__additional-info { + color: var(--accent); + } + &__industry { padding: 2px 36px; color: var(--light-white); @@ -165,6 +169,12 @@ color: var(--accent); } } + + &__skills { + display: flex; + flex-flow: wrap; + margin-bottom: 9px; + } } .message-modal { diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.spec.ts b/projects/social_platform/src/app/office/shared/info-card/info-card.component.spec.ts similarity index 64% rename from projects/social_platform/src/app/office/shared/project-card/project-card.component.spec.ts rename to projects/social_platform/src/app/office/shared/info-card/info-card.component.spec.ts index 961e58bc9..f2e3eb5e9 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.spec.ts +++ b/projects/social_platform/src/app/office/shared/info-card/info-card.component.spec.ts @@ -2,29 +2,27 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; -import { ProjectCardComponent } from "./project-card.component"; import { of } from "rxjs"; import { IndustryService } from "@services/industry.service"; -import { Project } from "@models/project.model"; +import { InfoCardComponent } from "./info-card.component"; describe("ProjectCardComponent", () => { - let component: ProjectCardComponent; - let fixture: ComponentFixture; + let component: InfoCardComponent; + let fixture: ComponentFixture; beforeEach(async () => { const industrySpy = jasmine.createSpyObj([{ industries: of([]) }]); await TestBed.configureTestingModule({ - imports: [ProjectCardComponent], + imports: [InfoCardComponent], providers: [{ provide: IndustryService, useValue: industrySpy }], }).compileComponents(); }); beforeEach(() => { - fixture = TestBed.createComponent(ProjectCardComponent); + fixture = TestBed.createComponent(InfoCardComponent); component = fixture.componentInstance; - component.project = Project.default(); fixture.detectChanges(); }); diff --git a/projects/social_platform/src/app/office/shared/project-card/project-card.component.ts b/projects/social_platform/src/app/office/shared/info-card/info-card.component.ts similarity index 85% rename from projects/social_platform/src/app/office/shared/project-card/project-card.component.ts rename to projects/social_platform/src/app/office/shared/info-card/info-card.component.ts index d3a912e4f..748872f2c 100644 --- a/projects/social_platform/src/app/office/shared/project-card/project-card.component.ts +++ b/projects/social_platform/src/app/office/shared/info-card/info-card.component.ts @@ -11,14 +11,18 @@ import { SubscriptionService } from "@office/services/subscription.service"; import { InviteService } from "@office/services/invite.service"; import { ClickOutsideModule } from "ng-click-outside"; import { Router } from "@angular/router"; +import { User } from "@auth/models/user.model"; +import { TagComponent } from "@ui/components/tag/tag.component"; +import { YearsFromBirthdayPipe } from "@corelib"; +import { Invite } from "@office/models/invite.model"; /** - * Компонент карточки проекта + * Компонент карточки информации с разным наполнением, в зависимости от контекста */ @Component({ - selector: "app-project-card", - templateUrl: "./project-card.component.html", - styleUrl: "./project-card.component.scss", + selector: "app-info-card", + templateUrl: "./info-card.component.html", + styleUrl: "./info-card.component.scss", standalone: true, imports: [ CommonModule, @@ -28,16 +32,18 @@ import { Router } from "@angular/router"; ModalComponent, ButtonComponent, ClickOutsideModule, + TagComponent, + YearsFromBirthdayPipe, ], }) -export class ProjectCardComponent implements OnInit { +export class InfoCardComponent implements OnInit { private readonly inviteService = inject(InviteService); private readonly subscriptionService = inject(SubscriptionService); public readonly industryService = inject(IndustryService); private readonly router = inject(Router); - @Input() project?: Project; - @Input() type: "invite" | "project" = "project"; + @Input() info?: any; + @Input() type: "invite" | "projects" | "members" = "projects"; @Input() appereance: "my" | "subs" | "base" | "empty" = "base"; @Input() section: "projects" | "subscriptions" | "other" = "projects"; @Input() canDelete?: boolean | null = false; @@ -61,7 +67,7 @@ export class ProjectCardComponent implements OnInit { * Определяет, нужно ли показывать информацию о проекте */ shouldShowProjectInfo(): boolean { - return this.type === "project" && this.appereance !== "subs" && this.appereance !== "empty"; + return this.type === "projects" && this.appereance !== "subs" && this.appereance !== "empty"; } /** @@ -86,7 +92,7 @@ export class ProjectCardComponent implements OnInit { : this.appereance === "empty" && this.section === "subscriptions" ? "/assets/images/projects/shared/add-project.svg" : ""; - return this.project?.imageAddress || currentImageAddress; + return this.info?.imageAddress || this.info?.avatar || currentImageAddress; } /** @@ -104,7 +110,7 @@ export class ProjectCardComponent implements OnInit { * Обработка отклонения приглашения */ onRejectInvite(event: Event, inviteId: number): void { - if (!this.project || !inviteId) { + if (!this.info || !inviteId) { console.warn("Cannot reject invite: missing project or inviteId"); return; } @@ -113,7 +119,7 @@ export class ProjectCardComponent implements OnInit { this.inviteService.rejectInvite(inviteId).subscribe({ next: () => { - this.onRejectingInvite.emit(inviteId || this.project!.inviteId); + this.onRejectingInvite.emit(inviteId || this.info!.inviteId); }, error: error => { console.error("Error rejecting invite:", error); @@ -126,7 +132,7 @@ export class ProjectCardComponent implements OnInit { * Обработка принятия приглашения */ onAcceptInvite(event: Event, inviteId: number): void { - if (!this.project || !inviteId) { + if (!this.info || !inviteId) { console.warn("Cannot accept invite: missing project or inviteId"); return; } @@ -135,7 +141,7 @@ export class ProjectCardComponent implements OnInit { this.inviteService.acceptInvite(inviteId).subscribe({ next: () => { - this.onAcceptingInvite.emit(inviteId || this.project!.inviteId); + this.onAcceptingInvite.emit(inviteId || this.info!.inviteId); }, error: error => { console.error("Error accepting invite:", error); @@ -239,7 +245,7 @@ export class ProjectCardComponent implements OnInit { * Валидация входных параметров */ private validateInputs(): void { - if (this.appereance !== "empty" && !this.project) { + if (this.appereance !== "empty" && !this.info) { console.warn('ProjectCardComponent: project is required when appearance is not "empty"'); } } diff --git a/projects/social_platform/src/app/office/shared/member-card/member-card.component.html b/projects/social_platform/src/app/office/shared/member-card/member-card.component.html deleted file mode 100644 index be3bb2218..000000000 --- a/projects/social_platform/src/app/office/shared/member-card/member-card.component.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - -
    -
    - - -
    -
    -
    -

    {{ user.firstName }} {{ user.lastName }}

    - - @if (user.speciality) { -

    - {{ user.speciality }} - @if (user.speciality && user.birthday) { • } @if (user.birthday) { - {{ user.birthday | yearsFromBirthday }} - } -

    - } @if (user.skills && user.skills.length) { -
      - @for ( skill of user.skills.slice(0, 3); track skill.id ) { -
    • - {{ - skill.name.length > 10 ? skill.name.slice(0, 10) + " " + "..." : skill.name - }} -
    • - } -
    - } -
    -
    - -
    - - профиль - -
    -
    -
    -
    diff --git a/projects/social_platform/src/app/office/shared/member-card/member-card.component.scss b/projects/social_platform/src/app/office/shared/member-card/member-card.component.scss deleted file mode 100644 index d96f9f83c..000000000 --- a/projects/social_platform/src/app/office/shared/member-card/member-card.component.scss +++ /dev/null @@ -1,177 +0,0 @@ -/** @format */ - -@use "styles/responsive"; - -.card { - &__body { - position: relative; - display: flex; - flex-direction: column; - height: 170px; - padding: 15px 0 12px; - background-color: var(--light-white); - border: 0.5px solid var(--medium-for-outline); - border-radius: var(--rounded-xl); - } - - &__info { - display: flex; - flex-shrink: 0; - gap: 50px; - align-items: center; - justify-content: space-evenly; - - &--vacancies, - &--collaborators { - display: flex; - gap: 4px; - align-items: center; - color: var(--grey-for-text); - } - } - - &__badge { - position: absolute; - top: 30px; - left: 0; - z-index: 1; - } - - &__subscribe-badge { - display: block; - color: var(--accent); - cursor: pointer; - } - - &__photo { - position: absolute; - top: -35px; - left: 50%; - z-index: 2; - transform: translateX(-50%); - } - - &__content { - display: flex; - flex: 1; - flex-direction: column; - align-items: center; - justify-content: flex-start; - padding-top: 40px; - overflow: hidden; - text-align: center; - } - - &__head { - display: flex; - flex-direction: column; - flex-shrink: 0; - align-items: center; - margin-bottom: 8px; - } - - &__name { - max-width: 200px; - overflow: hidden; - color: var(--black); - text-overflow: ellipsis; - white-space: nowrap; - } - - &__additional-info { - color: var(--accent); - } - - &__industry { - padding: 2px 36px; - color: var(--light-white); - background-color: var(--green); - border-radius: var(--rounded-xl); - } - - &__description { - display: flex; - flex: 1; - flex-direction: column; - align-items: center; - max-height: 20px; - overflow: hidden; - color: var(--dark-grey); - text-align: center; - word-break: break-word; - - &.invite-description { - justify-content: center; - } - } - - &__user { - margin-bottom: 2px; - color: var(--grey-for-text); - - &--invite { - color: var(--accent); - } - } - - &__project { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - margin-top: auto; - border-radius: var(--rounded-xl); - - &--text { - color: var(--white); - } - } - - &__skills { - display: flex; - flex-flow: wrap; - margin-top: 6px; - margin-bottom: 9px; - } -} - -.message-modal { - display: flex; - flex-direction: column; - align-items: center; - max-width: 402px; - - &__title { - margin: 18px 0; - color: var(--black); - text-align: center; - } - - &__text { - color: var(--dark-grey); - text-align: center; - } - - &__button { - margin-top: 18px; - } -} - -.unsubscribe-modal { - display: flex; - flex-direction: column; - align-items: center; - padding: 20px; - text-align: center; - - h3 { - margin-bottom: 16px; - color: var(--black); - } - - &__buttons { - display: flex; - gap: 12px; - margin-top: 16px; - } -} diff --git a/projects/social_platform/src/app/office/shared/member-card/member-card.component.spec.ts b/projects/social_platform/src/app/office/shared/member-card/member-card.component.spec.ts deleted file mode 100644 index 367e839bc..000000000 --- a/projects/social_platform/src/app/office/shared/member-card/member-card.component.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** @format */ - -import { ComponentFixture, TestBed } from "@angular/core/testing"; - -import { MemberCardComponent } from "./member-card.component"; -import { User } from "@auth/models/user.model"; -import { UserRolePipe } from "@core/pipes/user-role.pipe"; -import { of } from "rxjs"; -import { AuthService } from "@auth/services"; -import { DayjsPipe, YearsFromBirthdayPipe } from "projects/core"; - -describe("MemberCardComponent", () => { - let component: MemberCardComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - const authSpy = { - roles: of([]), - }; - - await TestBed.configureTestingModule({ - imports: [MemberCardComponent, UserRolePipe, DayjsPipe, YearsFromBirthdayPipe], - providers: [{ provide: AuthService, useValue: authSpy }], - }).compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(MemberCardComponent); - - component = fixture.componentInstance; - component.user = User.default(); - - fixture.detectChanges(); - }); - - it("should create", () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/projects/social_platform/src/app/office/shared/member-card/member-card.component.ts b/projects/social_platform/src/app/office/shared/member-card/member-card.component.ts deleted file mode 100644 index 66434d20a..000000000 --- a/projects/social_platform/src/app/office/shared/member-card/member-card.component.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** @format */ - -import { Component, Input, OnInit } from "@angular/core"; -import { User } from "@auth/models/user.model"; -import { YearsFromBirthdayPipe } from "projects/core"; -import { UserRolePipe } from "@core/pipes/user-role.pipe"; -import { TagComponent } from "@ui/components/tag/tag.component"; -import { AsyncPipe } from "@angular/common"; -import { AvatarComponent } from "@ui/components/avatar/avatar.component"; -import { ButtonComponent } from "@ui/components"; - -/** - * Компонент карточки участника команды - * - * Функциональность: - * - Отображает основную информацию об участнике (аватар, имя, возраст) - * - Показывает роль пользователя через UserRolePipe - * - Вычисляет возраст на основе даты рождения через YearsFromBirthdayPipe - * - Отображает теги с дополнительной информацией - * - Простой компонент для отображения без интерактивности - * - * Входные параметры: - * @Input user - объект пользователя (обязательный) - * - * Выходные события: отсутствуют - * - * Используемые пайпы: - * - YearsFromBirthdayPipe - вычисление возраста по дате рождения - * - UserRolePipe - форматирование роли пользователя - * - AsyncPipe - работа с асинхронными данными - * - * Примечание: Компонент предназначен только для отображения информации - */ -@Component({ - selector: "app-member-card", - templateUrl: "./member-card.component.html", - styleUrl: "./member-card.component.scss", - standalone: true, - imports: [ - AvatarComponent, - TagComponent, - AsyncPipe, - UserRolePipe, - YearsFromBirthdayPipe, - ButtonComponent, - ], -}) -export class MemberCardComponent implements OnInit { - constructor() {} - - @Input({ required: true }) user!: User; - - ngOnInit(): void {} -} diff --git a/projects/social_platform/src/app/office/shared/skills-basket/skills-basket.component.html b/projects/social_platform/src/app/office/shared/skills-basket/skills-basket.component.html index 13cd697c7..406fbe7fd 100644 --- a/projects/social_platform/src/app/office/shared/skills-basket/skills-basket.component.html +++ b/projects/social_platform/src/app/office/shared/skills-basket/skills-basket.component.html @@ -3,7 +3,10 @@
    @for (skill of value(); track skill.id) { -
    +
    {{ skill.name }} {{ vacancy.role }}

    @if (vacancy.requiredSkills.length) { @for (skill of vacancy.requiredSkills.slice(0, 5); track $index) { - {{ + {{ skill.name }} @if (vacancy.specialization) { - {{ + {{ vacancy.specialization ? vacancy.specialization : "" }} } } @if (vacancy.requiredSkills.length > 5) { From 3dc0fe412a229181731ace09d3be9709b4d62bbf Mon Sep 17 00:00:00 2001 From: Awakich Date: Fri, 19 Sep 2025 12:49:52 +0300 Subject: [PATCH 072/126] change styles for skill basket in edit project to vacancy step --- .../office/shared/skills-basket/skills-basket.component.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/projects/social_platform/src/app/office/shared/skills-basket/skills-basket.component.scss b/projects/social_platform/src/app/office/shared/skills-basket/skills-basket.component.scss index 97631e0c5..aae0109d2 100644 --- a/projects/social_platform/src/app/office/shared/skills-basket/skills-basket.component.scss +++ b/projects/social_platform/src/app/office/shared/skills-basket/skills-basket.component.scss @@ -32,11 +32,10 @@ align-items: center; justify-content: center; padding: 5px 25px; - border-radius: var(--rounded-xxl); color: var(--accent); background-color: var(--white); border: 0.5px solid var(--accent); - + border-radius: var(--rounded-xxl); &-delete-icon { margin-left: 10px; From 66bf9d186fcd9750e3e415d3b73746ab90089d1a Mon Sep 17 00:00:00 2001 From: Awakich Date: Sun, 21 Sep 2025 17:21:47 +0300 Subject: [PATCH 073/126] change project detail page, add vacancies, team page, add service to program & project for cashing --- .../src/app/core/pipes/user-links.pipe.ts | 9 +- .../app/office/members/members.component.html | 2 +- .../src/app/office/models/project.model.ts | 1 + .../program/detail/detail.component.html | 2 +- .../program/detail/detail.component.scss | 2 +- .../office/program/detail/detail.component.ts | 15 +- .../office/program/detail/detail.resolver.ts | 7 +- .../shared/news-card/news-card.component.scss | 4 +- .../program/services/program-data.service.ts | 17 + .../projects/dashboard/dashboard.component.ts | 2 +- .../projects/detail/detail.component.html | 237 +++++++- .../projects/detail/detail.component.scss | 134 +++++ .../projects/detail/detail.component.ts | 127 ++++- .../office/projects/detail/detail.resolver.ts | 5 +- .../office/projects/detail/detail.routes.ts | 10 + .../projects/detail/info/info.component.html | 531 ++++++------------ .../projects/detail/info/info.component.scss | 275 +++------ .../projects/detail/info/info.component.ts | 167 +++--- .../detail/services/project-data.service.ts | 31 + .../project-direction-card.component.html | 17 + .../project-direction-card.component.scss | 64 +++ .../project-direction-card.component.ts | 21 + .../projects/detail/team/team.component.html | 9 + .../projects/detail/team/team.component.scss | 0 .../projects/detail/team/team.component.ts | 29 + .../detail/vacancies/vacancies.component.html | 1 + .../detail/vacancies/vacancies.component.scss | 0 .../detail/vacancies/vacancies.component.ts | 14 + .../office/projects/projects.component.html | 8 +- .../shared/info-card/info-card.component.html | 7 +- .../shared/info-card/info-card.component.scss | 3 +- .../shared/info-card/info-card.component.ts | 2 +- .../shared/news-card/news-card.component.html | 32 +- .../shared/news-card/news-card.component.scss | 30 +- .../app/ui/components/tag/tag.component.scss | 2 +- .../{ => helpers}/dashboardItemBuilder.ts | 5 +- .../app/utils/helpers/directionItemBuilder.ts | 22 + .../src/assets/icons/svg/anchor.svg | 3 + .../src/assets/icons/svg/deadline.svg | 3 + .../src/assets/icons/svg/goal.svg | 7 + .../src/assets/icons/svg/graph.svg | 3 + .../src/assets/icons/svg/in-search.svg | 3 + .../src/assets/icons/svg/key.svg | 3 + .../src/assets/icons/svg/label.svg | 3 + .../src/assets/icons/svg/mail.svg | 3 + .../src/assets/icons/svg/person.svg | 4 +- .../src/assets/icons/svg/smile.svg | 3 + .../assets/icons/symbol/svg/sprite.css.svg | 2 +- 48 files changed, 1127 insertions(+), 754 deletions(-) create mode 100644 projects/social_platform/src/app/office/program/services/program-data.service.ts create mode 100644 projects/social_platform/src/app/office/projects/detail/services/project-data.service.ts create mode 100644 projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.html create mode 100644 projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss create mode 100644 projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.ts create mode 100644 projects/social_platform/src/app/office/projects/detail/team/team.component.html create mode 100644 projects/social_platform/src/app/office/projects/detail/team/team.component.scss create mode 100644 projects/social_platform/src/app/office/projects/detail/team/team.component.ts create mode 100644 projects/social_platform/src/app/office/projects/detail/vacancies/vacancies.component.html create mode 100644 projects/social_platform/src/app/office/projects/detail/vacancies/vacancies.component.scss create mode 100644 projects/social_platform/src/app/office/projects/detail/vacancies/vacancies.component.ts rename projects/social_platform/src/app/utils/{ => helpers}/dashboardItemBuilder.ts (71%) create mode 100644 projects/social_platform/src/app/utils/helpers/directionItemBuilder.ts create mode 100644 projects/social_platform/src/assets/icons/svg/anchor.svg create mode 100644 projects/social_platform/src/assets/icons/svg/deadline.svg create mode 100644 projects/social_platform/src/assets/icons/svg/goal.svg create mode 100644 projects/social_platform/src/assets/icons/svg/graph.svg create mode 100644 projects/social_platform/src/assets/icons/svg/in-search.svg create mode 100644 projects/social_platform/src/assets/icons/svg/key.svg create mode 100644 projects/social_platform/src/assets/icons/svg/label.svg create mode 100644 projects/social_platform/src/assets/icons/svg/mail.svg create mode 100644 projects/social_platform/src/assets/icons/svg/smile.svg diff --git a/projects/social_platform/src/app/core/pipes/user-links.pipe.ts b/projects/social_platform/src/app/core/pipes/user-links.pipe.ts index 96df24d35..304f7559c 100644 --- a/projects/social_platform/src/app/core/pipes/user-links.pipe.ts +++ b/projects/social_platform/src/app/core/pipes/user-links.pipe.ts @@ -41,9 +41,14 @@ export class UserLinksPipe implements PipeTransform { value.includes("api.selcdn.ru/v1") ) { const valueTrimed = value.replace(/^https?:\/\//, ""); + const iconName = + value.includes("procollab_media") || value.includes("api.selcdn.ru/v1") + ? "file" + : value.includes("@") + ? "mail" + : "link"; return { - iconName: - value.includes("procollab_media") || value.includes("api.selcdn.ru/v1") ? "file" : "link", + iconName, tag: valueTrimed, }; } diff --git a/projects/social_platform/src/app/office/members/members.component.html b/projects/social_platform/src/app/office/members/members.component.html index c1b902a34..f1f374473 100644 --- a/projects/social_platform/src/app/office/members/members.component.html +++ b/projects/social_platform/src/app/office/members/members.component.html @@ -14,7 +14,7 @@

    Участники

    @for (member of members; track member.id) {
  • - +
  • } diff --git a/projects/social_platform/src/app/office/models/project.model.ts b/projects/social_platform/src/app/office/models/project.model.ts index cf773ff26..42417d312 100644 --- a/projects/social_platform/src/app/office/models/project.model.ts +++ b/projects/social_platform/src/app/office/models/project.model.ts @@ -50,6 +50,7 @@ export class Project { links!: string[]; draft!: boolean; leader!: number; + leaderInfo?: { firstName: string; lastName: string }; partnerProgramsTags?: string[]; partnerProgramId!: number | null; partnerProgram!: PartnerProgramInfo | null; diff --git a/projects/social_platform/src/app/office/program/detail/detail.component.html b/projects/social_platform/src/app/office/program/detail/detail.component.html index aaf17659c..38744964c 100644 --- a/projects/social_platform/src/app/office/program/detail/detail.component.html +++ b/projects/social_platform/src/app/office/program/detail/detail.component.html @@ -94,7 +94,7 @@ (hide)="hideTooltip()" > } @if (!program.isUserManager) { - + } diff --git a/projects/social_platform/src/app/office/program/detail/detail.component.scss b/projects/social_platform/src/app/office/program/detail/detail.component.scss index 05e056f87..317bfdc79 100644 --- a/projects/social_platform/src/app/office/program/detail/detail.component.scss +++ b/projects/social_platform/src/app/office/program/detail/detail.component.scss @@ -67,7 +67,7 @@ &__actions { display: grid; - grid-template-columns: 2fr 2fr 1fr 2fr 2fr; + grid-template-columns: 2fr 2fr 2fr 2fr 2fr; gap: 20px; align-items: center; } diff --git a/projects/social_platform/src/app/office/program/detail/detail.component.ts b/projects/social_platform/src/app/office/program/detail/detail.component.ts index a3d342fcd..ad82f7011 100644 --- a/projects/social_platform/src/app/office/program/detail/detail.component.ts +++ b/projects/social_platform/src/app/office/program/detail/detail.component.ts @@ -13,10 +13,11 @@ import { ProjectAdditionalService } from "@office/projects/edit/services/project import { Project } from "@office/models/project.model"; import { ProjectService } from "@office/services/project.service"; import { HttpErrorResponse } from "@angular/common/http"; -import { map, Subscription, tap } from "rxjs"; +import { filter, map, Subscription, take, tap } from "rxjs"; import { Program } from "@office/program/models/program.model"; import { AutosizeModule } from "ngx-autosize"; import { TooltipComponent } from "@ui/components/tooltip/tooltip.component"; +import { ProgramDataService } from "../services/program-data.service"; /** * Основной компонент детальной страницы программы @@ -75,6 +76,7 @@ export class ProgramDetailComponent implements OnInit, OnDestroy { private readonly route = inject(ActivatedRoute); private readonly router = inject(Router); private readonly location = inject(Location); + private readonly programDataService = inject(ProgramDataService); programId?: number; @@ -115,12 +117,15 @@ export class ProgramDetailComponent implements OnInit, OnDestroy { this.updatePageStates(url); }); - const program$ = this.route.data + const program$ = this.programDataService.program$ .pipe( - map(r => r["data"]), + filter(program => !!program), + take(1), tap(program => { - this.program = program; - this.registerDateExpired = Date.now() > Date.parse(program.datetimeRegistrationEnds); + if (program) { + this.program = program; + this.registerDateExpired = Date.now() > Date.parse(program.datetimeRegistrationEnds); + } }) ) .subscribe(); diff --git a/projects/social_platform/src/app/office/program/detail/detail.resolver.ts b/projects/social_platform/src/app/office/program/detail/detail.resolver.ts index e9bde86de..f0de67f2c 100644 --- a/projects/social_platform/src/app/office/program/detail/detail.resolver.ts +++ b/projects/social_platform/src/app/office/program/detail/detail.resolver.ts @@ -4,6 +4,8 @@ import { inject } from "@angular/core"; import { ActivatedRouteSnapshot, ResolveFn } from "@angular/router"; import { ProgramService } from "@office/program/services/program.service"; import { Program } from "@office/program/models/program.model"; +import { tap } from "rxjs"; +import { ProgramDataService } from "../services/program-data.service"; /** * Резолвер для получения детальной информации о программе @@ -37,6 +39,9 @@ import { Program } from "@office/program/models/program.model"; */ export const ProgramDetailResolver: ResolveFn = (route: ActivatedRouteSnapshot) => { const programService = inject(ProgramService); + const programDataService = inject(ProgramDataService); - return programService.getOne(route.params["programId"]); + return programService + .getOne(route.params["programId"]) + .pipe(tap(program => programDataService.setProgram(program))); }; diff --git a/projects/social_platform/src/app/office/program/detail/shared/news-card/news-card.component.scss b/projects/social_platform/src/app/office/program/detail/shared/news-card/news-card.component.scss index d01210377..7ce4e6b11 100644 --- a/projects/social_platform/src/app/office/program/detail/shared/news-card/news-card.component.scss +++ b/projects/social_platform/src/app/office/program/detail/shared/news-card/news-card.component.scss @@ -1,5 +1,5 @@ -@use "styles/typography"; @use "styles/responsive"; +@use "styles/typography"; .card { padding: 24px 12px; @@ -57,7 +57,6 @@ &__title { display: flex; align-items: center; - margin-right: 8px; } &__top { @@ -77,6 +76,7 @@ /* stylelint-disable value-no-vendor-prefix */ &__text { white-space: break-spaces; + @include typography.body-10; p { display: -webkit-box; diff --git a/projects/social_platform/src/app/office/program/services/program-data.service.ts b/projects/social_platform/src/app/office/program/services/program-data.service.ts new file mode 100644 index 000000000..0ba988f01 --- /dev/null +++ b/projects/social_platform/src/app/office/program/services/program-data.service.ts @@ -0,0 +1,17 @@ +/** @format */ + +import { Injectable } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; +import { Program } from "../models/program.model"; + +@Injectable({ + providedIn: "root", +}) +export class ProgramDataService { + private programSubject$ = new BehaviorSubject(undefined); + program$ = this.programSubject$.asObservable(); + + setProgram(program: Program): void { + return this.programSubject$.next(program); + } +} diff --git a/projects/social_platform/src/app/office/projects/dashboard/dashboard.component.ts b/projects/social_platform/src/app/office/projects/dashboard/dashboard.component.ts index 7a6b05d9b..c2d9908e9 100644 --- a/projects/social_platform/src/app/office/projects/dashboard/dashboard.component.ts +++ b/projects/social_platform/src/app/office/projects/dashboard/dashboard.component.ts @@ -6,7 +6,7 @@ import { ActivatedRoute, Router } from "@angular/router"; import { Project } from "@office/models/project.model"; import { Subscription } from "rxjs"; import { DashboardItemComponent } from "./shared/dashboardItem/dashboardItem.component"; -import { DashboardItem, dashboardItemBuilder } from "@utils/dashboardItemBuilder"; +import { DashboardItem, dashboardItemBuilder } from "@utils/helpers/dashboardItemBuilder"; @Component({ selector: "app-dashboard", diff --git a/projects/social_platform/src/app/office/projects/detail/detail.component.html b/projects/social_platform/src/app/office/projects/detail/detail.component.html index cfd101ee5..08f8a82b8 100644 --- a/projects/social_platform/src/app/office/projects/detail/detail.component.html +++ b/projects/social_platform/src/app/office/projects/detail/detail.component.html @@ -1,36 +1,217 @@ -@if (project) {
    - +
    + +
    + @if(project) { +
    +
    + cover +
    + +

    {{ project.name }}

    +
    +
    + @if (authService.profile | async; as profile) { +
    +
    +
    + @if (project.presentationAddress) { + + + презентация + + + + } @else { + + рабочая зона + + } @if (profile.userType !== 1 && !(isInProject$ | async)) { + + написать + + } @else { + + чат проекта + + } + + @if (profile.id === project.leader) { + + редактировать + + } + + + команда + + вакансии + + @if (isInProject$ | async) { + + выйти из проекта + + + +
    +

    Выход из проекта

    + leave image +

    + Ты уверен, что хочешь выйти из проекта? Это действие нельзя будет отменить. +

    +
    + + Да + + + Нет, хочу остаться + +
    +
    +
    + } +
    +
    + } +
    + } + + +
    + + idea +

    Поддержать проект

    +

    Напишите лидеру проекта, чтобы обсудить оказание поддержки

    + @if (project?.links && project?.links?.length) { +
      + @for (link of project?.links; track $index) { + + } +
    + } +
    +
    + + +
    +

    Прежде чем выйти из проекта смените основателя!

    + +
    + + Хорошо + +
    +
    +
    + + +
    + + idea +

    Редактирование недоступно

    + +

    + Этот проект уже отправлен на конкурс.
    Изменения будут доступны только после + окончания конкурса. +

    + + Отмена +
    +
    +
    -} diff --git a/projects/social_platform/src/app/office/projects/detail/detail.component.scss b/projects/social_platform/src/app/office/projects/detail/detail.component.scss index c8158406f..cd4c91969 100644 --- a/projects/social_platform/src/app/office/projects/detail/detail.component.scss +++ b/projects/social_platform/src/app/office/projects/detail/detail.component.scss @@ -1,5 +1,8 @@ /** @format */ +@use "styles/responsive"; +@use "styles/typography"; + $detail-bar-height: 63px; $detail-bar-mb: 12px; @@ -16,3 +19,134 @@ $detail-bar-mb: 12px; padding-bottom: 12px; } } + +.info { + $body-slide: 15px; + + position: relative; + padding: 0; + background-color: transparent; + border: none; + border-radius: $body-slide; + + &__cover { + position: relative; + height: 136px; + border-radius: 15px 15px 0 0; + + img { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + } + } + + &__body { + position: relative; + z-index: 2; + padding: 14px 0 40px; + margin-bottom: 26px; + } + + &__avatar { + position: absolute; + bottom: -10px; + left: 50%; + display: block; + cursor: pointer; + background-color: var(--white); + border-radius: 50%; + + @include responsive.apply-desktop { + transform: translate(-50%, 50%); + } + } + + &__row { + display: flex; + gap: 20px; + align-items: center; + justify-content: center; + margin-top: 2px; + + @include responsive.apply-desktop { + justify-content: unset; + margin-top: 0; + } + } + + &__title { + overflow: hidden; + color: var(--black); + text-align: center; + text-overflow: ellipsis; + margin-top: 10px; + } + + &__text { + color: var(--dark-grey); + } + + &__actions { + display: grid; + grid-template-columns: 2fr 2fr 2fr 2fr 2fr; + gap: 20px; + align-items: center; + } + + &__edit { + display: block; + } +} + +.support { + padding-bottom: 110px; + + @include responsive.apply-desktop { + width: 470px; + } + + &__cross { + position: absolute; + top: 15px; + right: 15px; + z-index: 2; + cursor: pointer; + } + + &__img { + position: absolute; + right: 0; + bottom: 0; + height: 195px; + + @include responsive.apply-desktop { + height: unset; + } + } + + &__title { + color: var(--black); + text-align: center; + + @include typography.bold-body-16; + + @include responsive.apply-desktop { + @include typography.heading-4; + } + } + + &__text { + margin: 20px 0; + color: var(--black); + + @include responsive.apply-desktop { + max-width: 260px; + } + } +} diff --git a/projects/social_platform/src/app/office/projects/detail/detail.component.ts b/projects/social_platform/src/app/office/projects/detail/detail.component.ts index 611f97454..b7624fd9f 100644 --- a/projects/social_platform/src/app/office/projects/detail/detail.component.ts +++ b/projects/social_platform/src/app/office/projects/detail/detail.component.ts @@ -1,12 +1,19 @@ /** @format */ -import { Component, OnDestroy, OnInit } from "@angular/core"; -import { map, Subscription } from "rxjs"; -import { ActivatedRoute, RouterOutlet } from "@angular/router"; +import { Component, inject, OnDestroy, OnInit } from "@angular/core"; +import { concatMap, filter, map, Subscription, take } from "rxjs"; +import { ActivatedRoute, Router, RouterModule, RouterOutlet } from "@angular/router"; import { Project } from "@models/project.model"; import { AuthService } from "@auth/services"; -import { AsyncPipe } from "@angular/common"; -import { BarComponent } from "@ui/components"; +import { AsyncPipe, CommonModule, Location } from "@angular/common"; +import { ButtonComponent } from "@ui/components"; +import { BackComponent, IconComponent } from "@uilib"; +import { AvatarComponent } from "@ui/components/avatar/avatar.component"; +import { IndustryService } from "@office/services/industry.service"; +import { ModalComponent } from "@ui/components/modal/modal.component"; +import { ProjectService } from "@office/services/project.service"; +import { UserLinksPipe } from "@core/pipes/user-links.pipe"; +import { ProjectDataService } from "./services/project-data.service"; /** * Компонент детального просмотра проекта @@ -29,16 +36,65 @@ import { BarComponent } from "@ui/components"; templateUrl: "./detail.component.html", styleUrl: "./detail.component.scss", standalone: true, - imports: [RouterOutlet, AsyncPipe, BarComponent], + imports: [ + CommonModule, + RouterOutlet, + RouterModule, + AsyncPipe, + IconComponent, + UserLinksPipe, + BackComponent, + ButtonComponent, + AvatarComponent, + ModalComponent, + ], }) export class ProjectDetailComponent implements OnInit, OnDestroy { - constructor(private readonly route: ActivatedRoute, private readonly authService: AuthService) {} + public readonly authService = inject(AuthService); + private readonly projectService = inject(ProjectService); + public readonly industryService = inject(IndustryService); + private readonly route = inject(ActivatedRoute); + private readonly router = inject(Router); + private readonly location = inject(Location); + private readonly projectDataService = inject(ProjectDataService); + + /** Массив всех подписок компонента */ + subscriptions$: Subscription[] = []; + + /** Данные текущего проекта */ + project?: Project; + + // Состояние подписки и модальных окон + isLeaveProjectModalOpen = false; // Флаг модального окна выхода + isEditDisable = false; // Флаг недоступности редактирования + isEditDisableModal = false; // Флаг недоступности редактирования для модалки + openSupport = false; // Флаг модального окна поддержки + leaderLeaveModal = false; // Флаг модального окна предупреждения лидера + + isTeamPage = false; + isVacanciesPage = false; ngOnInit(): void { // Подписка на данные проекта из резолвера - const projectSub$ = this.route.data.pipe(map(r => r["data"][0])).subscribe(project => { - this.project = project; + const projectSub$ = this.projectDataService.project$ + .pipe( + filter(project => !!project), + take(1) + ) + .subscribe(project => { + this.project = project; + + if (project?.partnerProgram) { + this.isEditDisable = project.partnerProgram?.isSubmitted; + } + }); + + this.updatePageStates(); + + this.location.onUrlChange(url => { + this.updatePageStates(url); }); + projectSub$ && this.subscriptions$.push(projectSub$); } @@ -47,14 +103,59 @@ export class ProjectDetailComponent implements OnInit, OnDestroy { this.subscriptions$.forEach($ => $.unsubscribe()); } - /** Массив всех подписок компонента */ - subscriptions$: Subscription[] = []; + /** + * Закрытие модального окна выхода из проекта + */ + onCloseLeaveProjectModal(): void { + this.isLeaveProjectModalOpen = false; + } - /** Данные текущего проекта */ - project?: Project; + /** + * Закрытие модального окна для невозможности редактировать проект + */ + onUnableEditingProject(): void { + if (this.isEditDisable) { + this.isEditDisableModal = true; + } else { + this.isEditDisableModal = false; + } + } + + /** + * Выход из проекта + */ + onLeave() { + this.route.data + .pipe(map(r => r["data"][0])) + .pipe(concatMap(p => this.projectService.leave(p.id))) + .subscribe( + () => { + this.router + .navigateByUrl("/office/projects/my") + .then(() => console.debug("Route changed from ProjectInfoComponent")); + }, + () => { + this.leaderLeaveModal = true; // Показываем предупреждение для лидера + } + ); + } + + /** + * Закрытие модального окна предупреждения лидера + */ + onCloseLeaderLeaveModal(): void { + this.leaderLeaveModal = false; + } /** Observable, определяющий участие текущего пользователя в проекте */ isInProject$ = this.authService.profile.pipe( map(profile => this.project?.collaborators.map(person => person.userId).includes(profile.id)) ); + + private updatePageStates(url?: string) { + const currentUrl = url || this.router.url; + + this.isTeamPage = currentUrl.includes("/team"); + this.isVacanciesPage = currentUrl.includes("/vacancies"); + } } diff --git a/projects/social_platform/src/app/office/projects/detail/detail.resolver.ts b/projects/social_platform/src/app/office/projects/detail/detail.resolver.ts index 74fe52ae6..aa1df6833 100644 --- a/projects/social_platform/src/app/office/projects/detail/detail.resolver.ts +++ b/projects/social_platform/src/app/office/projects/detail/detail.resolver.ts @@ -2,11 +2,12 @@ import { inject } from "@angular/core"; import { ActivatedRouteSnapshot, ResolveFn } from "@angular/router"; -import { forkJoin, of, switchMap } from "rxjs"; +import { forkJoin, of, switchMap, tap } from "rxjs"; import { ProjectService } from "@services/project.service"; import { Project } from "@models/project.model"; import { SubscriptionService } from "@office/services/subscription.service"; import { ProjectSubscriber } from "@office/models/project-subscriber.model"; +import { ProjectDataService } from "./services/project-data.service"; /** * Резолвер для загрузки данных проекта и его подписчиков @@ -26,8 +27,10 @@ export const ProjectDetailResolver: ResolveFn<[Project, ProjectSubscriber[]]> = ) => { const projectService = inject(ProjectService); const subscriptionService = inject(SubscriptionService); + const projectDataService = inject(ProjectDataService); return projectService.getOne(Number(route.paramMap.get("projectId"))).pipe( + tap(project => projectDataService.setProject(project)), switchMap(project => { return forkJoin([of(project), subscriptionService.getSubscribers(project.id)]); }) diff --git a/projects/social_platform/src/app/office/projects/detail/detail.routes.ts b/projects/social_platform/src/app/office/projects/detail/detail.routes.ts index bb902698e..ffd65256a 100644 --- a/projects/social_platform/src/app/office/projects/detail/detail.routes.ts +++ b/projects/social_platform/src/app/office/projects/detail/detail.routes.ts @@ -11,6 +11,8 @@ import { ProjectChatResolver } from "@office/projects/detail/chat/chat.resolver" import { ProjectDetailResolver } from "@office/projects/detail/detail.resolver"; import { NewsDetailComponent } from "@office/projects/detail/news-detail/news-detail.component"; import { NewsDetailResolver } from "@office/projects/detail/news-detail/news-detail.resolver"; +import { ProjectTeamComponent } from "./team/team.component"; +import { ProjectVacanciesComponent } from "./vacancies/vacancies.component"; /** * Конфигурация маршрутов для детального просмотра проекта @@ -48,6 +50,14 @@ export const PROJECT_DETAIL_ROUTES: Routes = [ }, ], }, + { + path: "vacancies", + component: ProjectVacanciesComponent, + }, + { + path: "team", + component: ProjectTeamComponent, + }, { path: "responses", component: ProjectResponsesComponent, diff --git a/projects/social_platform/src/app/office/projects/detail/info/info.component.html b/projects/social_platform/src/app/office/projects/detail/info/info.component.html index 1aeefd769..10c25101d 100644 --- a/projects/social_platform/src/app/office/projects/detail/info/info.component.html +++ b/projects/social_platform/src/app/office/projects/detail/info/info.component.html @@ -1,386 +1,207 @@ -@if (project$ | async; as project) { +@if (project) {
    -
    -
    - cover - -
    - @if (authService.profile | async; as profile) { -
    -
    -

    {{ project.name }}

    -
    - @if (industryService.industries | async; as industries) { -

    - @if (industryService.getIndustry(industries, project.industry); as industry) { - {{ industry.name }} - } -

    - } @if (project.region) { -

    - - {{ project.region }} -

    - } -
    -
    -
    - @if (project.presentationAddress) { - - - Презентация проекта - - - - } @if (profile.userType !== 1 && !(isInProject | async)) { - - Поддержать проект - - } - - @if (profile.id === project.leader) { - - Редактировать - - } - - @if (isInProject | async) { -
    - +
    +
    +
    +
    +

    метаданные

    +
    - -
    -

    Выход из проекта

    - leave image -

    - Ты уверен, что хочешь выйти из проекта? Это действие нельзя будет отменить. +

      +
    • + + @if (industryService.industries | async; as industries) { +

      + @if (industryService.getIndustry(industries, project.industry); as industry) { + {{ industry?.name }} + }

      -
      - - Да - - - Нет, хочу остаться - -
      -
    -
    - } + } + + +
  • + +

    {{ project.region ?? "не указан" }}

    +
  • + +
  • + +

    {{ project.trl ?? "0" }}

    +
  • + +
  • + +

    {{ project.implementationDeadline ?? "не указана" }}

    +
  • + +
  • + +

    + {{ project.leaderInfo?.lastName }} {{ project.leaderInfo?.firstName }} +

    +
  • +
    - } -
    -
    -
    -
    -

    О проекте

    -
    - - {{ project.viewsCount }} + +
    +
    +
    +

    о проекте

    +
    -
    - @if (project.description) { -
    -

    - @if (descriptionExpandable) { -
    - {{ readFullDescription ? "Скрыть" : "Читать полностью" }} + @if (project.description) { +
    +

    + @if (descriptionExpandable) { +
    + {{ readFullDescription ? "Скрыть" : "Читать полностью" }} +
    + }
    }
    - } -
    -
    - @if (authService.profile | async; as profile) { -
    - @if (project.leader === profile.id) { - - } @for (n of news; track n.id) { - - } -
    - } -
    - @if (project.vacancies.length; as vacanciesLength) { -
    -

    Вакансии

    - @if (project.vacancies; as vacancies) { @if (vacancies) { -
      - @for (vacancy of vacancies.slice(0, 3); track vacancy.id) { - - } -
    - } -
    - @if (vacancies) { -
      - @for (vacancy of vacancies.slice(3); track vacancy.id) { - - } -
    - } -
    - } - -
  • -
    - - {{ vacancy.role }} - +
    +
  • -
    - @if (vacanciesLength > 3) { -
    - {{ readAllVacancies ? "Скрыть" : "Читать полностью" }} -
    - } -
    - } @if(profileId === project.leader){ - - - Добавить вакансию - - - - } @if (project.achievements.length) { -
    -

    Достижения

    - @if (project.achievements; as achievements) { @if (achievements) { -
      - @for (a of achievements.slice(0, 3); track a.id) { -
    • -

      {{ a.status }}

      -

      {{ a.title }}

      -
    • + +
        + @for (link of project.links; track $index) { +
      • + @if (link | userLinks; as l) { + + + {{ l.tag }} + + } +
      • + } +
      +
    } - - } -
    - @if (achievements) { -
      - @for (a of achievements.slice(3); track a.id) { -
    • -

      {{ a.status }}

      -

      {{ a.title }}

      -
    • + + + @if (project.links.length) { +
      +
      +

      в поиске

      + +
      + +
        +
      • + +

        2 ляма

        +
      • + +
      • + +

        расширение нннадо

        +
      • + +
      • + +

        аренда сервера

        +
      • + +
      • + +

        публикация в СМИ

        +
      • +
      +
      + } @if (project.achievements.length) { +
      +
      +

      достижения

      + +
      + + @if (project.achievements; as achievements) { @if (achievements) { +
        + @for (a of achievements.slice(0, 3); track a.id) { +
      • +

        {{ a.status }}

        +

        {{ a.title }}

        +
      • + } +
      } -
    - } -
    - } @if (project.achievements.length > 3) { -
    - {{ readAllAchievements ? "Скрыть" : "Читать полностью" }} -
    - } -
    - } @if (project.links.length) { - - } - -
    - -
    - - idea -

    Поддержать проект

    -

    Напишите лидеру проекта, чтобы обсудить оказание поддержки

    -
      - @for (link of project.links; track $index) { -
    } - - } - -
    - - - -
    -

    Прежде чем выйти из проекта смените основателя!

    - -
    - - Хорошо - +
    -
    - - -
    - - idea -

    Редактирование недоступно

    - -

    - Этот проект уже отправлен на конкурс.
    Изменения будут доступны только после окончания - конкурса. -

    + - Отмена -
    -
    } diff --git a/projects/social_platform/src/app/office/projects/detail/info/info.component.scss b/projects/social_platform/src/app/office/projects/detail/info/info.component.scss index fa1c9dc31..4ca8f64ba 100644 --- a/projects/social_platform/src/app/office/projects/detail/info/info.component.scss +++ b/projects/social_platform/src/app/office/projects/detail/info/info.component.scss @@ -37,20 +37,24 @@ padding-bottom: 0; } - &__bar { - margin-bottom: 12px; - } - &__main { display: grid; grid-template-columns: 1fr; - grid-gap: 8px; - align-items: start; + } - @include responsive.apply-desktop { - grid-template-columns: 2fr 1fr; - grid-gap: 16px; - } + &__details { + display: grid; + grid-template-columns: 2fr 5fr 3fr; + grid-gap: 20px; + } + + &__right { + display: flex; + flex-direction: column; + } + + &__left { + width: 157px; } &__aside { @@ -65,9 +69,10 @@ &__section { padding: 24px; - background-color: var(--white); - border: 1px solid var(--medium-grey-for-outline); - border-radius: var(--rounded-xl); + margin-bottom: 14px; + background-color: var(--light-white); + border: 0.5px solid var(--medium-grey-for-outline); + border-radius: var(--rounded-lg); } &__info { @@ -84,10 +89,6 @@ } } - &__about { - padding: 0 24px; - } - &__news { grid-row-start: 4; @@ -95,6 +96,14 @@ grid-row-start: unset; } } + + &__directions { + display: grid; + grid-template-columns: repeat(5, 1fr); + align-items: center; + grid-gap: 20px; + margin-top: 14px; + } } .info { @@ -179,35 +188,6 @@ color: var(--black); text-align: center; text-overflow: ellipsis; - - @include typography.heading-4; - - @include responsive.apply-desktop { - text-align: unset; - - @include typography.heading-2; - } - } - - &__text { - color: var(--dark-grey); - } - - &__industry { - margin-right: 20px; - - @include responsive.apply-desktop { - margin-right: 40px; - } - } - - &__geo { - display: flex; - align-items: center; - - i { - margin-right: 5px; - } } &__right { @@ -279,52 +259,27 @@ } } -.team { - &__title { - margin-bottom: 12px; - color: var(--black); - } - - ul > * { - display: block; - margin-bottom: 12px; - } - - @include expandable-list; -} - .about { + padding: 24px; + background-color: var(--light-white); + border-radius: var(--rounded-lg); + &__head { display: flex; align-items: center; justify-content: space-between; + margin-bottom: 8px; + border-bottom: 0.5px solid var(--accent); - @include responsive.apply-desktop { - display: block; + &--icon { + color: var(--accent); } } &__title { - margin-bottom: 12px; - color: var(--black); - } - - &__views { - display: flex; - align-items: center; - color: var(--gray); - - @include typography.body-12; - - @include responsive.apply-desktop { - display: none; - } - - i { - margin-right: 5px; - } + margin-bottom: 8px; + color: var(--accent); } - /* stylelint-disable value-no-vendor-prefix */ &__text { p { @@ -363,77 +318,61 @@ } } -.achievements { - &__title { - margin-bottom: 12px; - color: var(--black); +.lists { + &__section { + display: flex; + justify-content: space-between; + margin-bottom: 8px; + border-bottom: 0.5px solid var(--accent); } - li { - &:not(:last-child) { - margin-bottom: 12px; - } + &__list { + display: flex; + flex-direction: column; + gap: 8px; } - @include expandable-list; -} - -.vacancies { - &__title { - margin-bottom: 12px; - color: var(--black); + &__icon { + color: var(--accent); } - li { - &:not(:last-child) { - margin-bottom: 12px; - } + &__title { + margin-bottom: 8px; + color: var(--accent); } - @include expandable-list; -} - -.vacancy { - &__info { + &__item { display: flex; align-items: center; - justify-content: space-between; - margin-bottom: 3px; - } + gap: 6px; - &__title { - text-decoration: underline; + &--status { + padding: 8px; + border-radius: var(--rounded-xl); + border: 0.5px solid var(--medium-grey-for-outline); + } - &--disabled { + &--title { color: var(--black); - text-decoration: none; - pointer-events: none; } - } - - &__action { - color: var(--accent); - text-decoration: underline; - transition: color 0.2s; - &:hover { - color: var(--accent-dark); + i { + border-radius: var(--rounded-xl); + padding: 6px; + border: 0.5px solid var(--medium-grey-for-outline); + background-color: var(--light-white); } - } - &__requirements { - color: var(--dark-grey); - } -} + p { + color: var(--accent); + } -.achievement { - &__place { - color: var(--black); + span { + cursor: pointer; + } } - &__title { - color: var(--dark-grey); - } + @include expandable-list; } .news { @@ -457,76 +396,4 @@ &:hover { color: var(--accent-dark); } - - @include typography.body-14; -} - -.links { - overflow: hidden; - - &__title { - margin-bottom: 12px; - } - - &__item { - &:not(:last-child) { - margin-bottom: 12px; - } - } - - ul { - overflow: hidden; - - span { - overflow: hidden; - text-overflow: ellipsis; - } - } -} - -.support { - padding-bottom: 110px; - - @include responsive.apply-desktop { - width: 470px; - } - - &__cross { - position: absolute; - top: 15px; - right: 15px; - z-index: 2; - cursor: pointer; - } - - &__img { - position: absolute; - right: 0; - bottom: 0; - height: 195px; - - @include responsive.apply-desktop { - height: unset; - } - } - - &__title { - color: var(--black); - text-align: center; - - @include typography.bold-body-16; - - @include responsive.apply-desktop { - @include typography.heading-4; - } - } - - &__text { - margin: 20px 0; - color: var(--black); - - @include responsive.apply-desktop { - max-width: 260px; - } - } } diff --git a/projects/social_platform/src/app/office/projects/detail/info/info.component.ts b/projects/social_platform/src/app/office/projects/detail/info/info.component.ts index 434f1f81c..ca48362ae 100644 --- a/projects/social_platform/src/app/office/projects/detail/info/info.component.ts +++ b/projects/social_platform/src/app/office/projects/detail/info/info.component.ts @@ -38,10 +38,13 @@ import { map, noop, of, + switchMap, take, - withLatestFrom, } from "rxjs"; import { ProjectMemberCardComponent } from "../shared/project-member-card/project-member-card.component"; +import { EditorSubmitButtonDirective } from "@ui/directives/editor-submit-button.directive"; +import { DirectionItem, directionItemBuilder } from "@utils/helpers/directionItemBuilder"; +import { ProjectDirectionCard } from "../shared/project-direction-card/project-direction-card.component"; /** * КОМПОНЕНТ ДЕТАЛЬНОЙ ИНФОРМАЦИИ О ПРОЕКТЕ @@ -90,13 +93,14 @@ import { ProjectMemberCardComponent } from "../shared/project-member-card/projec NewsFormComponent, NewsCardComponent, CommonModule, + ProjectDirectionCard, ], }) export class ProjectInfoComponent implements OnInit, AfterViewInit, OnDestroy { constructor( private readonly route: ActivatedRoute, // Сервис для работы с активным маршрутом private readonly router: Router, // Сервис для навигации - public readonly industryService: IndustryService, // Сервис для работы с отраслями + public readonly industryService: IndustryService, // Сервис сфер проекта public readonly authService: AuthService, // Сервис аутентификации private readonly navService: NavService, // Сервис навигации private readonly projectNewsService: ProjectNewsService, // Сервис новостей проекта @@ -104,15 +108,11 @@ export class ProjectInfoComponent implements OnInit, AfterViewInit, OnDestroy { private readonly cdRef: ChangeDetectorRef // Сервис для ручного запуска обнаружения изменений ) {} - // Observable с данными проекта из резолвера - project$?: Observable = this.route.parent?.data.pipe(map(r => r["data"][0])); // Observable с подписчиками проекта projSubscribers$?: Observable = this.route.parent?.data.pipe(map(r => r["data"][1])); profileId!: number; // ID текущего пользователя - // Observable с вакансиями проекта - vacancies$: Observable = this.route.data.pipe(map(r => r["data"])); subscriptions$: Subscription[] = []; // Массив подписок для очистки /** @@ -122,6 +122,50 @@ export class ProjectInfoComponent implements OnInit, AfterViewInit, OnDestroy { ngOnInit(): void { this.navService.setNavTitle("Профиль проекта"); + const projectSub$ = + this.route.parent?.data + .pipe( + map(r => r["data"][0]), + switchMap(project => { + return this.authService.getUser(project.leader).pipe( + map(user => { + return { + ...project, + leaderInfo: { + firstName: user.firstName, + lastName: user.lastName, + }, + }; + }) + ); + }) + ) + .subscribe({ + next: (project: Project) => { + this.project = project; + + if (project) { + this.directions = directionItemBuilder( + 5, + ["проблема", "целевая аудитория", "актуаль-сть", "цели", "партнеры"], + ["key", "smile", "graph", "goal", "team"], + [ + this.project?.problem, + this.project?.targetAudience, + this.project?.actuality, + "", + "", + ] + )!; + } + + setTimeout(() => { + this.checkDescriptionExpandable(); + this.cdRef.detectChanges(); + }, 0); + }, + }) ?? Subscription.EMPTY; + // Загрузка новостей проекта const news$ = this.projectNewsService .fetchNews(this.route.snapshot.params["projectId"]) @@ -146,14 +190,7 @@ export class ProjectInfoComponent implements OnInit, AfterViewInit, OnDestroy { this.profileId = profile.id; }); - const projectEditSub$ = - this.project$?.subscribe(project => { - if (project.partnerProgram) { - this.isEditDisable = project.partnerProgram?.isSubmitted; - } - }) ?? Subscription.EMPTY; - - this.subscriptions$.push(news$, profileId$, projectEditSub$); + this.subscriptions$.push(projectSub$, news$, profileId$); } // Ссылки на элементы DOM @@ -166,16 +203,9 @@ export class ProjectInfoComponent implements OnInit, AfterViewInit, OnDestroy { * Перемещает новости в контентную область на десктопе, проверяет необходимость кнопки "Читать полностью" */ ngAfterViewInit(): void { - // На десктопе перемещаем новости в основной контент if (containerSm < window.innerWidth) { this.contentEl?.nativeElement.append(this.newsEl?.nativeElement); } - - // Проверяем, нужна ли кнопка "Читать полностью" для описания - const descElement = this.descEl?.nativeElement; - this.descriptionExpandable = descElement?.clientHeight < descElement?.scrollHeight; - - this.cdRef.detectChanges(); } /** @@ -214,7 +244,11 @@ export class ProjectInfoComponent implements OnInit, AfterViewInit, OnDestroy { readAllVacancies = false; // Флаг показа всех вакансий readAllMembers = false; // Флаг показа всех участников isCompleted = false; // Флаг завершенности проекта - leaderLeaveModal = false; // Флаг модального окна предупреждения лидера + + // Данные о проекте + project?: Project; + + directions: DirectionItem[] = []; /** * Добавление новой новости @@ -225,7 +259,7 @@ export class ProjectInfoComponent implements OnInit, AfterViewInit, OnDestroy { .addNews(this.route.snapshot.params["projectId"], news) .subscribe(newsRes => { this.newsFormComponent?.onResetForm(); - this.news.unshift(newsRes); // Добавляем новость в начало списка + this.news.unshift(newsRes); }); } @@ -278,84 +312,28 @@ export class ProjectInfoComponent implements OnInit, AfterViewInit, OnDestroy { * @param id - ID удаляемого участника */ onRemoveMember(id: Collaborator["userId"]) { - this.project$ - ?.pipe(concatMap(project => this.projectService.removeColloborator(project.id, id))) + this.projectService + .removeColloborator(this.route.snapshot.params["projectId"], id) .subscribe(() => { - location.reload(); // Перезагружаем страницу для обновления данных + location.reload(); }); } - /** - * Выход из проекта - */ - onLeave() { - this.project$?.pipe(concatMap(p => this.projectService.leave(p.id))).subscribe( - () => { - this.router - .navigateByUrl("/office/projects/my") - .then(() => console.debug("Route changed from ProjectInfoComponent")); - }, - () => { - this.leaderLeaveModal = true; // Показываем предупреждение для лидера - } - ); - } - /** * Передача лидерства другому участнику * @param id - ID нового лидера */ onTransferOwnership(id: Collaborator["userId"]) { - this.project$ - ?.pipe(concatMap(project => this.projectService.switchLeader(project.id, id))) - .subscribe(() => { - location.reload(); - }); - } - - // Состояние подписки и модальных окон - isLeaveProjectModalOpen = false; // Флаг модального окна выхода - isEditDisable = false; // Флаг недоступности редактирования - isEditDisableModal = false; // Флаг недоступности редактирования для модалки - - /** - * Закрытие модального окна предупреждения лидера - */ - onCloseLeaderLeaveModal(): void { - this.leaderLeaveModal = false; - } - - /** - * Закрытие модального окна выхода из проекта - */ - onCloseLeaveProjectModal(): void { - this.isLeaveProjectModalOpen = false; - } - - /** - * Закрытие модального окна для невозможности редактировать проект - */ - onUnableEditingProject(): void { - if (this.isEditDisable) { - this.isEditDisableModal = true; - } else { - this.isEditDisableModal = false; - } + this.projectService.switchLeader(this.route.snapshot.params["projectId"], id).subscribe(() => { + location.reload(); + }); } - openSupport = false; // Флаг модального окна поддержки - - // Проверка участия пользователя в проекте - isInProject = this.project$?.pipe( - concatMap(project => forkJoin([of(project), this.authService.profile.pipe(take(1))])), - map(([project, profile]) => project.collaborators.map(c => c.userId).includes(profile.id)) - ); - /** - * Развертывание/свертывание описания проекта - * @param elem - HTML элемент с описанием - * @param expandedClass - CSS класс для развернутого состояния - * @param isExpanded - текущее состояние (развернуто/свернуто) + * Раскрытие/сворачивание описания профиля + * @param elem - DOM элемент описания + * @param expandedClass - CSS класс для раскрытого состояния + * @param isExpanded - текущее состояние (раскрыто/свернуто) */ onExpandDescription(elem: HTMLElement, expandedClass: string, isExpanded: boolean): void { expandElement(elem, expandedClass, isExpanded); @@ -370,4 +348,15 @@ export class ProjectInfoComponent implements OnInit, AfterViewInit, OnDestroy { getSkillsNames(vacancy: Vacancy) { return vacancy.requiredSkills.map((s: any) => s.name).join(" • "); } + + private checkDescriptionExpandable(): void { + const descElement = this.descEl?.nativeElement; + + if (!descElement || !this.project?.description) { + this.descriptionExpandable = false; + return; + } + + this.descriptionExpandable = descElement.scrollHeight > descElement.clientHeight; + } } diff --git a/projects/social_platform/src/app/office/projects/detail/services/project-data.service.ts b/projects/social_platform/src/app/office/projects/detail/services/project-data.service.ts new file mode 100644 index 000000000..8fa8d6701 --- /dev/null +++ b/projects/social_platform/src/app/office/projects/detail/services/project-data.service.ts @@ -0,0 +1,31 @@ +/** @format */ + +import { Injectable } from "@angular/core"; +import { Project } from "@office/models/project.model"; +import { BehaviorSubject, filter, map } from "rxjs"; + +@Injectable({ + providedIn: "root", +}) +export class ProjectDataService { + private projectSubject = new BehaviorSubject(undefined); + project$ = this.projectSubject.asObservable(); + + setProject(project: Project) { + this.projectSubject.next(project); + } + + getTeam() { + return this.project$.pipe( + map(project => project?.collaborators), + filter(team => !!team) + ); + } + + getVacancies() { + return this.project$.pipe( + map(project => project?.vacancies), + filter(vacancies => !!vacancies) + ); + } +} diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.html b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.html new file mode 100644 index 000000000..907301d65 --- /dev/null +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.html @@ -0,0 +1,17 @@ + + +
    + +

    {{ direction }}

    +
    + + +
    +
    +

    {{ direction }}

    + +
    + +

    {{ about }}

    +
    +
    diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss new file mode 100644 index 000000000..9bbf39900 --- /dev/null +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss @@ -0,0 +1,64 @@ +.direction-card { + cursor: pointer; + border: 0.5px solid var(--medium-grey-for-outline); + background-color: var(--light-white); + border-radius: var(--rounded-xl); + padding: 20px 6px 12px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 50px; + width: 70px; + position: relative; + text-align: center; + + &__icon { + border-radius: var(--rounded-xl); + padding: 6px; + border: 0.5px solid var(--medium-grey-for-outline); + background-color: var(--light-white); + color: var(--accent); + position: absolute; + top: -24px; + left: 50%; + transform: translate(-50%, 50%); + } + + &__text { + width: 130%; + color: var(--grey-button); + } +} + +.cancel { + display: flex; + flex-direction: column; + width: 350px; + height: 175px; + padding: 24px; + max-height: calc(100vh - 40px); + overflow-y: auto; + + &__top { + display: flex; + align-items: center; + justify-content: space-between; + padding-bottom: 8px; + margin-bottom: 8px; + border-bottom: 0.5px solid var(--accent); + } + + &__title { + color: var(--accent); + text-align: center; + } + + &__icon { + color: var(--accent); + } + + &__text { + color: var(--black); + } +} diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.ts b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.ts new file mode 100644 index 000000000..89a0f2a48 --- /dev/null +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.ts @@ -0,0 +1,21 @@ +/** @format */ + +import { CommonModule } from "@angular/common"; +import { Component, Input } from "@angular/core"; +import { ModalComponent } from "@ui/components/modal/modal.component"; +import { IconComponent } from "@uilib"; + +@Component({ + selector: "app-project-direction-card", + templateUrl: "./project-direction-card.component.html", + styleUrl: "./project-direction-card.component.scss", + imports: [CommonModule, IconComponent, ModalComponent], + standalone: true, +}) +export class ProjectDirectionCard { + @Input() direction!: string; + @Input() icon!: string; + @Input() about!: string; + + isShowModal = false; +} diff --git a/projects/social_platform/src/app/office/projects/detail/team/team.component.html b/projects/social_platform/src/app/office/projects/detail/team/team.component.html new file mode 100644 index 000000000..a0ea62bda --- /dev/null +++ b/projects/social_platform/src/app/office/projects/detail/team/team.component.html @@ -0,0 +1,9 @@ + + +@if(team) { @if (team.length) { +
    + @for (collaborator of team; track $index) { + + } +
    +} } diff --git a/projects/social_platform/src/app/office/projects/detail/team/team.component.scss b/projects/social_platform/src/app/office/projects/detail/team/team.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/projects/social_platform/src/app/office/projects/detail/team/team.component.ts b/projects/social_platform/src/app/office/projects/detail/team/team.component.ts new file mode 100644 index 000000000..b0ba91ebe --- /dev/null +++ b/projects/social_platform/src/app/office/projects/detail/team/team.component.ts @@ -0,0 +1,29 @@ +/** @format */ + +import { CommonModule } from "@angular/common"; +import { Component, inject, OnInit } from "@angular/core"; +import { IconComponent } from "@uilib"; +import { ProjectDataService } from "../services/project-data.service"; +import { Collaborator } from "@office/models/collaborator.model"; +import { InfoCardComponent } from "@office/shared/info-card/info-card.component"; + +@Component({ + selector: "app-project-eam", + templateUrl: "./team.component.html", + styleUrl: "./team.component.scss", + imports: [CommonModule, IconComponent, InfoCardComponent], + standalone: true, +}) +export class ProjectTeamComponent implements OnInit { + private readonly projectDataService = inject(ProjectDataService); + + ngOnInit(): void { + this.projectDataService.getTeam().subscribe({ + next: team => { + this.team = team; + }, + }); + } + + team?: Collaborator[]; +} diff --git a/projects/social_platform/src/app/office/projects/detail/vacancies/vacancies.component.html b/projects/social_platform/src/app/office/projects/detail/vacancies/vacancies.component.html new file mode 100644 index 000000000..d200a0fdb --- /dev/null +++ b/projects/social_platform/src/app/office/projects/detail/vacancies/vacancies.component.html @@ -0,0 +1 @@ + diff --git a/projects/social_platform/src/app/office/projects/detail/vacancies/vacancies.component.scss b/projects/social_platform/src/app/office/projects/detail/vacancies/vacancies.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/projects/social_platform/src/app/office/projects/detail/vacancies/vacancies.component.ts b/projects/social_platform/src/app/office/projects/detail/vacancies/vacancies.component.ts new file mode 100644 index 000000000..45f9ead7a --- /dev/null +++ b/projects/social_platform/src/app/office/projects/detail/vacancies/vacancies.component.ts @@ -0,0 +1,14 @@ +/** @format */ + +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; +import { IconComponent } from "@uilib"; + +@Component({ + selector: "app-vacancies", + templateUrl: "./vacancies.component.html", + styleUrl: "./vacancies.component.scss", + imports: [CommonModule, IconComponent], + standalone: true, +}) +export class ProjectVacanciesComponent {} diff --git a/projects/social_platform/src/app/office/projects/projects.component.html b/projects/social_platform/src/app/office/projects/projects.component.html index 5f2678702..e67d8c12a 100644 --- a/projects/social_platform/src/app/office/projects/projects.component.html +++ b/projects/social_platform/src/app/office/projects/projects.component.html @@ -15,28 +15,28 @@ [links]="[ { link: '/office/projects/my', - linkText: 'Мои проекты', + linkText: 'мои проекты', iconName: 'main', isRouterLinkActiveOptions: false, }, { link: '/office/projects/subscriptions', - linkText: 'Мои подписки', + linkText: 'мои подписки', iconName: 'favourites', isRouterLinkActiveOptions: false, }, { link: '/office/projects/invites', - linkText: 'Мои приглашения', + linkText: 'мои приглашения', iconName: 'send', isRouterLinkActiveOptions: false, }, { link: '/office/projects/all', - linkText: 'Все проекты', + linkText: 'все проекты', iconName: 'folders', isRouterLinkActiveOptions: false, }, diff --git a/projects/social_platform/src/app/office/shared/info-card/info-card.component.html b/projects/social_platform/src/app/office/shared/info-card/info-card.component.html index 7f711d553..59c046224 100644 --- a/projects/social_platform/src/app/office/shared/info-card/info-card.component.html +++ b/projects/social_platform/src/app/office/shared/info-card/info-card.component.html @@ -98,7 +98,12 @@ -
    +
    @if (type === 'invite') {

    вас приглашает

    {{ info?.shortDescription }}

    diff --git a/projects/social_platform/src/app/office/shared/info-card/info-card.component.scss b/projects/social_platform/src/app/office/shared/info-card/info-card.component.scss index f44da27db..8f3be681a 100644 --- a/projects/social_platform/src/app/office/shared/info-card/info-card.component.scss +++ b/projects/social_platform/src/app/office/shared/info-card/info-card.component.scss @@ -105,7 +105,6 @@ flex: 1; flex-direction: column; align-items: center; - max-height: 20px; overflow: hidden; color: var(--dark-grey); text-align: center; @@ -173,6 +172,8 @@ &__skills { display: flex; flex-flow: wrap; + justify-content: center; + gap: 2px; margin-bottom: 9px; } } diff --git a/projects/social_platform/src/app/office/shared/info-card/info-card.component.ts b/projects/social_platform/src/app/office/shared/info-card/info-card.component.ts index 748872f2c..3aa314824 100644 --- a/projects/social_platform/src/app/office/shared/info-card/info-card.component.ts +++ b/projects/social_platform/src/app/office/shared/info-card/info-card.component.ts @@ -90,7 +90,7 @@ export class InfoCardComponent implements OnInit { this.appereance === "empty" && this.section === "projects" ? "/assets/images/projects/shared/add-project.svg" : this.appereance === "empty" && this.section === "subscriptions" - ? "/assets/images/projects/shared/add-project.svg" + ? "/assets/images/projects/shared/empty-subscriptions.svg" : ""; return this.info?.imageAddress || this.info?.avatar || currentImageAddress; } diff --git a/projects/social_platform/src/app/office/shared/news-card/news-card.component.html b/projects/social_platform/src/app/office/shared/news-card/news-card.component.html index c8f799555..94c1982e4 100644 --- a/projects/social_platform/src/app/office/shared/news-card/news-card.component.html +++ b/projects/social_platform/src/app/office/shared/news-card/news-card.component.html @@ -8,32 +8,29 @@ [alt]="feedItem.name" />
    -
    {{ feedItem.name }}
    -
    - {{ feedItem.datetimeCreated | dayjs: "format":"DD MMMM YYYY, HH:mm" }} -
    +
    {{ feedItem.name }}
    @if (isOwner) {
    - +
    @if (menuOpen) {
      @if (!editMode) { -
    • Редактировать
    • +
    • Редактировать
    • } -
    • Удалить
    • +
    • Удалить
    }
    }
    @if (feedItem.text) { -
    +
    @if (!editMode) { -

    +

    } @else { @if (editForm.get("text"); as text) { } } @@ -91,21 +88,26 @@

    }

    - } @if (user.userLanguages.length; as userLanguagesLength) { + } @if (user.projects.length; as projectsLength) {
    -

    языки

    - +

    проекты

    +
      - @for (p of user.userLanguages.slice(0, 2); track $index) { -
    • + @for (p of user.projects.slice(0, 3); track p.id) { +
    • }
    -
    - @if (user.userLanguages.length > 2) { +
    + @if (user.projects) {
      - @for (userLanguagesItem of user.userLanguages.slice(2); track $index) { + @for (project of user.projects.slice(3); track project.id) {
    • }
    }
    - -

    - {{ userLanguages.language }} {{ userLanguages.languageLevel }} -

    + +
    + +
    +

    {{ project.name }}

    + {{ project.collaborator?.role }} +
    +
    - @if (userLanguagesLength > 2) { -
    - {{ readAllLanguages ? "Скрыть" : "Читать полностью" }} -
    - } -
    - } @if (user.achievements.length; as achievementsLength) { -
    -
    -

    достижения

    - -
    -
      - @for (achievement of user.achievements.slice(0, 3); track achievement.id) { -
    • -

      {{ achievement.status }}

      -

      {{ achievement.title }}

      -
    • - } -
    -
    - @if (user.achievements.length) { -
      - @for (achievement of user.achievements.slice(3); track achievement.id) { -
    • -

      {{ achievement.status }}

      -

      {{ achievement.title }}

      -
    • - } -
    - } -
    - @if (achievementsLength > 3) { -
    - {{ readAllAchievements ? "Скрыть" : "Читать полностью" }} + @if (projectsLength > 3) { +
    + {{ readAllProjects ? "Скрыть" : "Читать полностью" }}
    }
    - } @if (user.links.length; as linksLength) { -
    -
    -

    контакты

    - -
    -
      - @for (link of user.links.slice(0, 3); track $index) { -
    • + } @if (user.programs.length; as programsLength) { +
      +
        + @for (p of user.programs.slice(0, 3); track p.id) { +
      • }
      -
      -
        - @for (link of user.links.slice(3); track $index) { -
      • - - +
        + @if (user.programs) { +
          + @for (program of user.programs.slice(3); track program.id) { +
        • +
        • }
        -
        - - @if (link | userLinks; as l) { - - - {{ l.tag }} - } +
      + +
      +
      + + program logo + +
      +
      - @if (linksLength > 3) { -
      - {{ readAllLinks ? "Скрыть" : "Читать полностью" }} + @if (programsLength > 3) { +
      + {{ readAllPrograms ? "Скрыть" : "Читать полностью" }}
      }
      diff --git a/projects/social_platform/src/app/office/profile/detail/main/main.component.scss b/projects/social_platform/src/app/office/profile/detail/main/main.component.scss index 3fdd1700c..2e82553d5 100644 --- a/projects/social_platform/src/app/office/profile/detail/main/main.component.scss +++ b/projects/social_platform/src/app/office/profile/detail/main/main.component.scss @@ -69,7 +69,7 @@ &__section { padding: 24px; - margin-bottom: 14px; + margin-bottom: 20px; background-color: var(--light-white); border: 0.5px solid var(--medium-grey-for-outline); border-radius: var(--rounded-lg); @@ -96,6 +96,14 @@ grid-row-start: unset; } } + + &__directions { + display: grid; + grid-template-columns: 1fr 1fr 3fr; + grid-gap: 20px; + align-items: center; + margin-top: 14px; + } } .info { @@ -309,7 +317,13 @@ &__list { display: flex; flex-direction: column; - gap: 8px; + gap: 10px; + + &--line { + display: flex; + gap: 10px; + flex-flow: wrap; + } } &__icon { @@ -321,6 +335,42 @@ color: var(--accent); } + &__index { + color: var(--accent); + } + + &__logo { + border-radius: var(--rounded-xxl); + } + + &__info { + display: flex; + flex-direction: column; + + &--text { + color: var(--black) !important; + } + + &--subtext { + color: var(--grey-for-text) !important; + } + + &--more { + color: var(--accent) !important; + } + } + + &__date { + display: flex; + flex-direction: column; + align-items: center; + border: 0.5px solid var(--medium-grey-for-outline); + width: 45px; + height: 45px; + padding: 5px; + border-radius: var(--rounded-xl); + } + &__item { display: flex; gap: 6px; @@ -348,6 +398,7 @@ justify-content: center; width: 20px; height: 20px; + padding-top: 1px; background-color: var(--light-white); border: 0.5px solid var(--medium-grey-for-outline); border-radius: var(--rounded-xl); @@ -387,3 +438,36 @@ color: var(--accent-dark); } } + +.cancel { + display: flex; + flex-direction: column; + width: 350px; + height: 175px; + max-height: calc(100vh - 40px); + padding: 24px; + overflow-y: auto; + + &__top { + display: flex; + align-items: center; + justify-content: space-between; + padding-bottom: 8px; + margin-bottom: 8px; + border-bottom: 0.5px solid var(--accent); + } + + &__title { + color: var(--accent); + text-align: center; + } + + &__icon { + color: var(--accent); + } + + &__text { + color: var(--black); + margin-bottom: 8px; + } +} diff --git a/projects/social_platform/src/app/office/profile/detail/main/main.component.ts b/projects/social_platform/src/app/office/profile/detail/main/main.component.ts index da5981a1c..d4da3a312 100644 --- a/projects/social_platform/src/app/office/profile/detail/main/main.component.ts +++ b/projects/social_platform/src/app/office/profile/detail/main/main.component.ts @@ -38,6 +38,8 @@ import { NewsFormComponent } from "@office/features/news-form/news-form.componen import { NewsCardComponent } from "@office/features/news-card/news-card.component"; import { ProfileDataService } from "../services/profile-date.service"; import { SoonCardComponent } from "@office/shared/soon-card/soon-card.component"; +import { ProjectDirectionCard } from "@office/projects/detail/shared/project-direction-card/project-direction-card.component"; +import { DirectionItem, directionItemBuilder } from "@utils/helpers/directionItemBuilder"; /** * Главный компонент страницы профиля пользователя @@ -83,6 +85,7 @@ import { SoonCardComponent } from "@office/shared/soon-card/soon-card.component" NewsCardComponent, NewsFormComponent, SoonCardComponent, + ProjectDirectionCard, ], changeDetection: ChangeDetectionStrategy.OnPush, }) @@ -97,6 +100,8 @@ export class ProfileMainComponent implements OnInit, AfterViewInit, OnDestroy { user?: User; loggedUserId?: number; + directions: DirectionItem[] = []; + subscriptions$: Subscription[] = []; /** * Инициализация компонента @@ -108,6 +113,14 @@ export class ProfileMainComponent implements OnInit, AfterViewInit, OnDestroy { .pipe(filter(user => !!user)) .subscribe({ next: user => { + if (user) { + this.directions = directionItemBuilder( + 2, + ["навыки", "достижения"], + ["squiz", "medal"], + ["", ""] + )!; + } this.user = user as User; }, }); @@ -176,6 +189,7 @@ export class ProfileMainComponent implements OnInit, AfterViewInit, OnDestroy { readAllModal = false; approveOwnSkillModal = false; + isShowModal = false; @ViewChild(NewsFormComponent) newsFormComponent?: NewsFormComponent; @ViewChild(NewsCardComponent) newsCardComponent?: NewsCardComponent; @@ -340,4 +354,8 @@ export class ProfileMainComponent implements OnInit, AfterViewInit, OnDestroy { onCloseModal(skillId: number) { this.openSkills[skillId] = false; } + + openWorkInfoModal(): void { + this.isShowModal = true; + } } diff --git a/projects/social_platform/src/app/office/projects/edit/services/project-goals.service.ts b/projects/social_platform/src/app/office/projects/edit/services/project-goals.service.ts index bc629cc7d..85cf86c76 100644 --- a/projects/social_platform/src/app/office/projects/edit/services/project-goals.service.ts +++ b/projects/social_platform/src/app/office/projects/edit/services/project-goals.service.ts @@ -298,50 +298,24 @@ export class ProjectGoalService { public saveGoals(projectId: number) { const goals = this.getGoalsData(); - const newGoalsWithIndex = goals - .map((g: any, idx: number) => ({ g, idx })) - .filter(item => !item.g.id); - - if (newGoalsWithIndex.length === 0) { - return of([]); - } - - const requests = newGoalsWithIndex.map(item => { - const payload: GoalPostForm = { - title: item.g.title, - completionDate: item.g.completionDate, - responsible: item.g.responsible, - isDone: item.g.isDone, - }; - - return this.projectService.postGoals(projectId, payload).pipe( - map((res: any) => ({ res, idx: item.idx })), - catchError(err => of({ __error: true, err, original: item.g, idx: item.idx })) - ); - }); - - return forkJoin(requests).pipe( + return this.projectService.postGoals(projectId, goals).pipe( tap(results => { - results.forEach((r: any) => { - if (r && r.__error) { - console.error("Failed to post goal", r.err, "original:", r.original); - return; - } - - const created = r.res; - const idx = r.idx; - - if (created && created.id !== undefined && created.id !== null) { - const formGroup = this.goals.at(idx); - if (formGroup) { - formGroup.get("id")?.setValue(created.id); + if (Array.isArray(results)) { + results.forEach((createdGoal: any, idx: number) => { + if (createdGoal && createdGoal.id !== undefined && createdGoal.id !== null) { + const formGroup = this.goals.at(idx); + if (formGroup) { + formGroup.get("id")?.setValue(createdGoal.id); + } } - } else { - console.warn("postGoal response has no id field:", r.res); - } - }); + }); + } this.syncGoalItems(this.goals); + }), + catchError(err => { + console.error("Error saving goals:", err); + return of({ __error: true, err, original: goals }); }) ); } diff --git a/projects/social_platform/src/app/office/services/project.service.ts b/projects/social_platform/src/app/office/services/project.service.ts index c98cab4d4..27a8106be 100644 --- a/projects/social_platform/src/app/office/services/project.service.ts +++ b/projects/social_platform/src/app/office/services/project.service.ts @@ -82,8 +82,15 @@ export class ProjectService { /** * Отправляем цель */ - postGoals(projectId: number, params: GoalPostForm) { - return this.apiService.post(`${this.PROJECTS_URL}/${projectId}/goals/`, params); + postGoals(projectId: number, params: GoalPostForm[]) { + return this.apiService.post(`${this.PROJECTS_URL}/${projectId}/goals/`, params); + } + + /** + * Редактирование цели + */ + editGoal(projectId: number, goalId: number, params: GoalPostForm) { + return this.apiService.post(`${this.PROJECTS_URL}/${projectId}/goals/${goalId}`, params); } /** diff --git a/projects/social_platform/src/app/office/shared/soon-card/soon-card.component.scss b/projects/social_platform/src/app/office/shared/soon-card/soon-card.component.scss index 232ad36fa..f77f39fe0 100644 --- a/projects/social_platform/src/app/office/shared/soon-card/soon-card.component.scss +++ b/projects/social_platform/src/app/office/shared/soon-card/soon-card.component.scss @@ -13,7 +13,7 @@ display: grid; place-items: center; margin-bottom: 4px; - border: 0.5px solid var(--medium-grey-for-outline); + border: 0.5px solid var(--grey-for-text); border-radius: var(--rounded-lg); } diff --git a/projects/social_platform/src/assets/icons/svg/squiz.svg b/projects/social_platform/src/assets/icons/svg/squiz.svg new file mode 100644 index 000000000..07f508e5d --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/squiz.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/svg/world-wide.svg b/projects/social_platform/src/assets/icons/svg/world-wide.svg new file mode 100644 index 000000000..ae67fab7b --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/world-wide.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg index a0dc95414..62deed68e 100644 --- a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg +++ b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 93c9914f3e2758de3b477f817c1db8c61d4f62c8 Mon Sep 17 00:00:00 2001 From: Awakich Date: Wed, 1 Oct 2025 15:14:22 +0300 Subject: [PATCH 094/126] add styles for profile page --- .../src/app/office/profile/detail/main/main.component.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/projects/social_platform/src/app/office/profile/detail/main/main.component.scss b/projects/social_platform/src/app/office/profile/detail/main/main.component.scss index 2e82553d5..837109a7c 100644 --- a/projects/social_platform/src/app/office/profile/detail/main/main.component.scss +++ b/projects/social_platform/src/app/office/profile/detail/main/main.component.scss @@ -321,8 +321,8 @@ &--line { display: flex; - gap: 10px; flex-flow: wrap; + gap: 10px; } } @@ -364,10 +364,10 @@ display: flex; flex-direction: column; align-items: center; - border: 0.5px solid var(--medium-grey-for-outline); width: 45px; height: 45px; padding: 5px; + border: 0.5px solid var(--medium-grey-for-outline); border-radius: var(--rounded-xl); } @@ -467,7 +467,7 @@ } &__text { - color: var(--black); margin-bottom: 8px; + color: var(--black); } } From d6f3191dd52914a447b5336f314fcba91c9f90ab Mon Sep 17 00:00:00 2001 From: Awakich Date: Fri, 3 Oct 2025 17:43:49 +0300 Subject: [PATCH 095/126] change avatar issue for skip registration, fix open vacancy component, feed routes, service --- .../features/detail/detail.component.html | 21 +++++++----- .../features/detail/detail.component.scss | 4 +++ .../features/detail/detail.component.ts | 33 +++++++++++-------- .../src/app/office/feed/feed.resolver.ts | 2 +- .../app/office/feed/services/feed.service.ts | 2 +- .../open-vacancy/open-vacancy.component.html | 14 ++++---- .../src/app/office/office.routes.ts | 2 +- .../stage-zero/stage-zero.component.ts | 18 +++++++++- .../project-main-step.component.ts | 5 +-- 9 files changed, 66 insertions(+), 35 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 373610f2a..91d57c69d 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 @@ -23,6 +23,7 @@ class="info__avatar" (click)="redirectDetailInfo()" [class.info__avatar--program]="listType === 'program'" + [style.width]="listType === 'project' || listType === 'profile' ? '110px' : '123px'" > @if (chatService.userOnlineStatusCache | async; as cache) { @if (listType === 'project' || listType === 'profile') { -

      +

      {{ listType === "project" ? info().name @@ -252,18 +257,18 @@

      - @if (!isInProject && profile?.userType !== 1) { @if (info().presentationAddress) { + @if (isInProject) { + + рабочая зона + + } @else { презентация - } } @else { - - рабочая зона - - } @if (profile) { @if (!isInProject) { + } @if (!isInProject) { > чат проекта - } } + }
      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 729db521e..97d6aea23 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 @@ -101,6 +101,10 @@ $detail-bar-mb: 12px; color: var(--black); text-align: center; text-overflow: ellipsis; + + &--project { + transform: translateX(-31%); + } } &__text { 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 4637a81a3..4a052bce6 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 @@ -331,12 +331,13 @@ export class DeatilComponent implements OnInit, OnDestroy { } }); + this.isInProfileInfo(); + this.subscriptions.push(projectSub$); } else if (this.listType === "program") { const program$ = this.programDataService.program$ .pipe( filter(program => !!program), - tap(program => { if (program) { this.info.set(program); @@ -369,17 +370,7 @@ export class DeatilComponent implements OnInit, OnDestroy { }, }); - const profileInfoSub$ = this.authService.profile.subscribe({ - next: profile => { - this.profile = profile; - - if (this.info()) { - this.isInProject = this.info() - ?.collaborators.map((person: Collaborator) => person.userId) - .includes(profile.id); - } - }, - }); + this.isInProfileInfo(); const profileIdDataSub$ = this.profileDataService .getProfileId() @@ -394,10 +385,26 @@ export class DeatilComponent implements OnInit, OnDestroy { this.isSubscriptionActive.set(r.isSubscribed); }); - this.subscriptions.push(profileDataSub$, profileIdDataSub$, profileInfoSub$); + this.subscriptions.push(profileDataSub$, profileIdDataSub$); } } + private isInProfileInfo(): void { + const profileInfoSub$ = this.authService.profile.subscribe({ + next: profile => { + this.profile = profile; + + if (this.info()) { + this.isInProject = this.info() + ?.collaborators.map((person: Collaborator) => person.userId) + .includes(profile.id); + } + }, + }); + + this.subscriptions.push(profileInfoSub$); + } + /** * Инициализация строки для back компонента в зависимости от типа данных */ diff --git a/projects/social_platform/src/app/office/feed/feed.resolver.ts b/projects/social_platform/src/app/office/feed/feed.resolver.ts index 8f4529270..91640b8db 100644 --- a/projects/social_platform/src/app/office/feed/feed.resolver.ts +++ b/projects/social_platform/src/app/office/feed/feed.resolver.ts @@ -29,6 +29,6 @@ export const FeedResolver: ResolveFn> = route => { return feedService.getFeed( 0, 20, - route.queryParams["includes"] ?? ["vacancy", "news", "project"] + route.queryParams["includes"] ?? ["vacancy", "news", "projects"] ); }; diff --git a/projects/social_platform/src/app/office/feed/services/feed.service.ts b/projects/social_platform/src/app/office/feed/services/feed.service.ts index 066060bbc..740d0cc1b 100644 --- a/projects/social_platform/src/app/office/feed/services/feed.service.ts +++ b/projects/social_platform/src/app/office/feed/services/feed.service.ts @@ -70,7 +70,7 @@ export class FeedService { // Обработка различных форматов параметра type if (type.length === 0) { // Если фильтры не выбраны, загружаем все типы по умолчанию - reqType = ["vacancy", "news", "project"].join(this.FILTER_SPLIT_SYMBOL); + reqType = ["vacancy", "news", "projects"].join(this.FILTER_SPLIT_SYMBOL); } else if (Array.isArray(type)) { // Если передан массив типов, объединяем их через разделитель reqType = type.join(this.FILTER_SPLIT_SYMBOL); diff --git a/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.html b/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.html index 25faa2e92..8db4e8104 100644 --- a/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.html +++ b/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.html @@ -1,10 +1,11 @@ +@if (feedItem) {
      - - newsItem.name + + newsItem.name
      -
      {{ feedItem.project.name }}
      +
      {{ feedItem.project?.name }}
      {{ feedItem.datetimeCreated | dayjs: "format":"DD MMMM YYYY, HH:mm" }}
      @@ -12,13 +13,13 @@

      Тебя ищут в проект - {{ - feedItem.project.name + {{ + feedItem.project?.name }}

      - @if (feedItem.requiredSkills.length; as skillsLength) { + @if (feedItem.requiredSkills?.length; as skillsLength) {
      Необходимые навыки
      @if (feedItem.requiredSkills; as requiredSkills) { @if (requiredSkills) {
        @@ -69,3 +70,4 @@

        >

      +} diff --git a/projects/social_platform/src/app/office/office.routes.ts b/projects/social_platform/src/app/office/office.routes.ts index 4fd4ebc23..6d68b3ce2 100644 --- a/projects/social_platform/src/app/office/office.routes.ts +++ b/projects/social_platform/src/app/office/office.routes.ts @@ -34,7 +34,7 @@ export const OFFICE_ROUTES: Routes = [ { path: "", pathMatch: "full", - redirectTo: "projects", + redirectTo: "feed", }, { path: "feed", diff --git a/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.ts b/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.ts index cbc3f4bef..74926fc69 100644 --- a/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.ts +++ b/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.ts @@ -733,7 +733,23 @@ export class OnboardingStageZeroComponent implements OnInit, OnDestroy { return; } - this.completeRegistration(3); + const onboardingSkipInfo = { + avatar: this.stageForm.get("avatar")?.value, + city: this.stageForm.get("city")?.value, + }; + + this.skipSubmitting.set(true); + this.authService + .saveProfile(onboardingSkipInfo) + .pipe(concatMap(() => this.authService.setOnboardingStage(3))) + .subscribe({ + next: () => this.completeRegistration(3), + error: error => { + this.skipSubmitting.set(false); + this.isModalErrorYear.set(true); + this.isModalErrorYearText.set(error.error?.message || "Ошибка сохранения"); + }, + }); } onSubmit(): void { diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.ts b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.ts index 9c9d5ade2..d71a149df 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.ts +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.ts @@ -97,10 +97,7 @@ export class ProjectMainStepComponent implements OnInit, OnDestroy { return this.projectGoalsService.getForm(); } - ngOnInit(): void { - console.log(this.hasGoals || this.hasLinks); - console.log(this.hasGoals, this.hasLinks); - } + ngOnInit(): void {} ngOnDestroy(): void { this.subscription.unsubscribe(); From 59f9e9621b11db1ae8ad513ce1475e11bb8593d0 Mon Sep 17 00:00:00 2001 From: Awakich Date: Fri, 3 Oct 2025 19:04:15 +0300 Subject: [PATCH 096/126] change logic of edit goals for goals items, fix links for edit project section --- .../src/app/office/models/goals.model.ts | 1 + .../office/projects/edit/edit.component.ts | 58 ++++++++++++++----- .../edit/services/project-goals.service.ts | 44 ++++++++++++++ .../project-main-step.component.html | 2 + .../project-main-step.component.ts | 8 +++ 5 files changed, 97 insertions(+), 16 deletions(-) diff --git a/projects/social_platform/src/app/office/models/goals.model.ts b/projects/social_platform/src/app/office/models/goals.model.ts index 75eb9fc02..7a4aac24f 100644 --- a/projects/social_platform/src/app/office/models/goals.model.ts +++ b/projects/social_platform/src/app/office/models/goals.model.ts @@ -18,6 +18,7 @@ class ResponsibleInfo { } export class GoalPostForm { + id?: number; title!: string; completionDate!: string; responsible!: number; 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 4fab6a9b1..cdea3a7b1 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 @@ -8,7 +8,7 @@ import { OnInit, signal, } from "@angular/core"; -import { FormGroup, ReactiveFormsModule } from "@angular/forms"; +import { FormArray, FormGroup, ReactiveFormsModule } from "@angular/forms"; import { ActivatedRoute, Router, RouterModule } from "@angular/router"; import { ErrorMessage } from "@error/models/error-message"; import { Invite } from "@models/invite.model"; @@ -31,6 +31,7 @@ import { distinctUntilChanged, finalize, map, + switchMap, tap, } from "rxjs"; import { CommonModule, AsyncPipe } from "@angular/common"; @@ -103,6 +104,7 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { private readonly projectVacancyService: ProjectVacancyService, private readonly projectTeamService: ProjectTeamService, private readonly projectAchievementsService: ProjectAchievementsService, + private readonly projectGoalsService: ProjectGoalService, private readonly snackBarService: SnackbarService, private readonly skillsService: SkillsService, private readonly projectAdditionalService: ProjectAdditionalService, @@ -164,6 +166,18 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { // Сигналы для работы с модальными окнами с текстом assignProjectToProgramModalMessage = signal(null); + // Геттеры для работы с целями + get goals(): FormArray { + return this.projectGoalsService.goals; + } + + /** + * Проверяет, есть ли цели для отображения + */ + get hasGoals(): boolean { + return this.goals.length > 0; + } + ngOnInit(): void { this.navService.setNavTitle("Создание проекта"); @@ -388,21 +402,18 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { this.setProjFormIsSubmitting(true); - this.projectService.updateProject(projectId, payload).subscribe({ - next: () => { - this.projectGoalService.saveGoals(projectId).subscribe({ - next: () => { - this.snackBarService.success("данные успешно сохранены"); - this.setProjFormIsSubmitting(false); - this.router.navigateByUrl(`/office/projects/${projectId}`); - }, - }); - }, - error: () => { - this.setProjFormIsSubmitting(false); - this.snackBarService.error("ошибка при сохранении данных"); - }, - }); + this.projectService + .updateProject(projectId, payload) + .pipe(switchMap(() => this.saveOrEditGoals(projectId))) + .subscribe({ + next: () => { + this.completeSubmitedProjectForm(projectId); + }, + error: () => { + this.setProjFormIsSubmitting(false); + this.snackBarService.error("ошибка при сохранении данных"); + }, + }); } // Методы для работы с модальными окнами @@ -423,6 +434,21 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { this.isAssignProjectToProgramModalOpen.set(false); } + public saveOrEditGoals(projectId: number) { + const goals = this.goals.value; + const hasExistingGoals = goals.some((g: GoalPostForm) => g.id); + + return hasExistingGoals + ? this.projectGoalService.editGoals(projectId) + : this.projectGoalService.saveGoals(projectId); + } + + private completeSubmitedProjectForm(projectId: number) { + this.snackBarService.success("данные успешно сохранены"); + this.setProjFormIsSubmitting(false); + this.router.navigateByUrl(`/office/projects/${projectId}`); + } + /** * Валидация дополнительных полей для публикации * Делегирует валидацию сервису diff --git a/projects/social_platform/src/app/office/projects/edit/services/project-goals.service.ts b/projects/social_platform/src/app/office/projects/edit/services/project-goals.service.ts index 85cf86c76..863247232 100644 --- a/projects/social_platform/src/app/office/projects/edit/services/project-goals.service.ts +++ b/projects/social_platform/src/app/office/projects/edit/services/project-goals.service.ts @@ -320,6 +320,50 @@ export class ProjectGoalService { ); } + public editGoals(projectId: number) { + const goals = this.getGoalsData(); + + const requests = goals.map(item => { + const payload: GoalPostForm = { + id: item.id, + title: item.title, + completionDate: item.completionDate, + responsible: item.responsible, + isDone: item.isDone, + }; + + return this.projectService.editGoal(projectId, item.id, payload).pipe( + map((res: any) => ({ res, idx: item.idx })), + catchError(err => of({ __error: true, err, original: item.g, idx: item.idx })) + ); + }); + + return forkJoin(requests).pipe( + tap(results => { + results.forEach((r: any) => { + if (r && r.__error) { + console.error("Failed to post goal", r.err, "original:", r.original); + return; + } + + const created = r.res; + const idx = r.idx; + + if (created && created.id !== undefined && created.id !== null) { + const formGroup = this.goals.at(idx); + if (formGroup) { + formGroup.get("id")?.setValue(created.id); + } + } else { + console.warn("postGoal response has no id field:", r.res); + } + }); + + this.syncGoalItems(this.goals); + }) + ); + } + /** * Сбрасывает состояние сервиса * Полезно при смене проекта или очистке формы 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 5f4c992c0..da74a78c0 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 @@ -399,7 +399,9 @@ добавить краткосрочную цель проекта +
      +
      @if (hasLinks) {

    + } @if (profileForm.get("coverImageAddress"); as coverImageAddress) { +
    + + + + +

    + обложка формата +
    + .JPG или .JPEG весом до 50МБ +

    + @if (coverImageAddress | controlError: "required") { +

    загрузите файл

    + } +
    +
    +
    } - - +

    -
    +
    +
    @if (profileForm.get("firstName"); as firstName) { -
    - +
    + @if (firstName | controlError: "required") {
    @@ -115,7 +147,7 @@
    } @if (profileForm.get("lastName"); as lastName) {
    - + -
    - @if (profileForm.get("birthday"); as birthday) { +
    + @if (profileForm.get("city"); as city) { +
    + + + @if (city | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } @if (profileForm.get("birthday"); as birthday) {
    - + }
    - } @if (profileForm.get("phoneNumber"); as phoneNumber) { + } +
    + +
    + @if (profileForm.get("userType"); as userType) { @if (userType.value !== 1) {
    - - тип аккаунта + @if (roles | async; as options) { + - @if (phoneNumber | controlError: "required") { + id="userType" + formControlName="userType" + placeholder="Эксперт" + [options]="options" + > + } @if (userType | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } } @if (profileForm.get("speciality"); as speciality) { +
    + +
    + +
    + @if (speciality | controlError: "required") {
    {{ errorMessage.VALIDATION_REQUIRED }}
    @@ -170,34 +248,21 @@
    }
    +
    -

    - Формат телефона должен соответствовать одному из таких форматов: +7 XXX XXX-XX-XX | - +375XXXXXXXXX | +995 (XXX) XX-XX-XX -

    - - @if (profileForm.get("speciality"); as speciality) { +
    + @if (profileForm.get("aboutMe"); as aboutMe) {
    - -
    - - - - -
    - @if (speciality | controlError: "required") { + + + @if (aboutMe | controlError: "required") {
    {{ errorMessage.VALIDATION_REQUIRED }}
    @@ -206,406 +271,388 @@ }
    - -
    - @if (profileForm.get("city"); as city) { -
    - - - @if (city | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    - } @if (profileForm.get("userType"); as userType) { @if (userType.value !== 1) { -
    - - @if (roles | async; as options) { - - } @if (userType | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    - } } @if (profileForm.get("aboutMe"); as aboutMe) { -
    - - - @if (aboutMe | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    - - } -
    } @if (editingStep === 'education') { -
    -
    - @if (profileForm.get("entryYear"); as entryYear) { -
    - - - - +
    +
    + @if (showEducationFields) { +
    + @if (profileForm.get("entryYear"); as entryYear) { +
    + + + + - @if (entryYear | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    + @if (entryYear | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } @if (profileForm.get("completionYear"); as completionYear) { +
    + + + + + @if (completionYear | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    } -
    - } @if (profileForm.get("completionYear"); as completionYear) { -
    - - - - - @if (completionYear | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    +
    + +
    + @if (profileForm.get("organizationName"); as organizationName) { +
    + + + @if (organizationName | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    } -
    - } -

    +

    - @if (profileForm.get("organizationName"); as organizationName) { -
    - - - @if (organizationName | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} +
    + @if (profileForm.get("description"); as description) { +
    + + + @if (description | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + }
    - } -
    - } @if (profileForm.get("description"); as description) { -
    - - - @if (description | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} + +
    + @if (profileForm.get("educationLevel"); as educationLevel) { +
    + + + + + @if (educationLevel | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + }
    - } -
    - } @if (profileForm.get("educationLevel"); as educationLevel) { -
    - - - - - @if (educationLevel | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} + +
    + @if (profileForm.get("educationStatus"); as educationStatus) { +
    + + + + + @if (educationStatus | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + }
    } -
    - } @if (profileForm.get("educationStatus"); as educationStatus) { -
    - - - - - @if (educationStatus | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    - } @if (profileForm.get("isMospolytechStudent"); as isMospolytechStudent) { -
    - - - @if (isMospolytechStudent | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    - } @if (profileForm.get("isMospolytechStudent")?.value) { @if (profileForm.get("studyGroup"); - as studyGroup) { -
    - - - @if (studyGroup | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    - } } -

    + добавить образование + + +

    -
    - - Добавить образование - - - - @if(educationItems().length || education.length){ @for (educationItem of education.value; - track $index) { -
    -
    -

    +

    + @if(educationItems().length || education.length){ @for (educationItem of education.value; + track $index) { +
    +

    {{ educationItem.organizationName }} +

    +

    @if(educationItem.entryYear && educationItem.completionYear) { - {{ educationItem.entryYear }} год - {{ educationItem.completionYear }} год } @else if + {{ educationItem.entryYear }} год • {{ educationItem.completionYear }} год } @else if (educationItem.entryYear && !educationItem.completionYear) { {{ educationItem.entryYear }} год } @else if (!educationItem.entryYear && educationItem.completionYear){ {{ educationItem.completionYear }} год } - - {{ educationItem.description }} {{ educationItem.educationStatus }} - {{ educationItem.educationLevel }}

    - + +
    +
    +

    + {{ educationItem.description }} +

    + +

    + {{ educationItem.educationLevel }} +

    + +

    + {{ educationItem.educationStatus }} +

    +
    + +
    +
    + +
    + +
    + +
    +
    +
    - - - + } }
    - } }
    } @if (editingStep === 'experience') { -
    -
    - @if (profileForm.get("entryYearWork"); as entryYearWork) { -
    - - - - +
    +
    + @if (showWorkFields){ +
    +
    + @if (profileForm.get("entryYearWork"); as entryYearWork) { +
    + + + + - @if (entryYearWork | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    - } @if (profileForm.get("completionYearWork"); as completionYearWork) { -
    - - - - + @if (entryYearWork | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } @if (profileForm.get("completionYearWork"); as completionYearWork) { +
    + + + + - @if (completionYearWork | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} + @if (completionYearWork | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + }
    +
    + +
    + @if (profileForm.get("organization"); as organization) { +
    + + + @if (organization | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    } -
    - } -
    +
    - @if (profileForm.get("organization"); as organization) { -
    - - - @if (organization | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} +
    + @if (profileForm.get("jobPosition"); as jobPosition) { +
    + + + @if (jobPosition | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + }
    - } -
    - } @if (profileForm.get("jobPosition"); as jobPosition) { -
    - - - @if (jobPosition | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} + +
    + @if (profileForm.get("descriptionWork"); as descriptionWork) { +
    + + + @if (descriptionWork | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + }
    } -
    - } @if (profileForm.get("descriptionWork"); as descriptionWork) { -
    - - - @if (descriptionWork | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    - } -
    + appearance="outline" + [disabled]="showWorkFields && !isWorkDirty" + [ngStyle]="{ + opacity: showWorkFields && !isWorkDirty ? '0.6' : '1', + cursor: showWorkFields && !isWorkDirty ? 'not-allowed' : 'pointer' + }" + class="profile__add-education" + (click)="addWork()" + > + добавить работу + + +
    -
    - - Добавить место работы - - - - @if(workItems().length || workExperience.length){ @for (workItem of workExperience.value; - track $index) { -
    -
    -

    +

    + @if(workItems().length || workExperience.length){ @for (workItem of workExperience.value; + track $index) { +
    +

    {{ workItem.organizationName }} +

    + +

    @if(workItem.entryYear && workItem.completionYear) { - {{ workItem.entryYear }} год - {{ workItem.completionYear }} год } @else if + {{ workItem.entryYear }} год • {{ workItem.completionYear }} год } @else if (workItem.entryYear && !workItem.completionYear) { {{ workItem.entryYear }} год } @else if (!workItem.entryYear && workItem.completionYear){ {{ workItem.completionYear }} год } - {{ workItem.description }} - {{ workItem.jobPosition }}

    - + +
    +
    +

    + {{ workItem.description }} +

    + +

    + {{ workItem.jobPosition }} +

    +
    + +
    +
    + +
    + +
    + +
    +
    +
    - - - + } }
    - } }
    } @if(editingStep === 'achievements'){ -
    -
    + } @if (editingStep === 'skills') { -
    -
    -
    -
    - -
    - - - - -
    -
    -
    +
    +
    +
    + +
    + +
    +
    -
    +
    @if (profileForm.get("skills"); as skills) { -
    +
    -
    -
    -
    -
    -
    - @if (profileForm.get("language"); as language) { -
    - - - - +
    +
    + @if (profileForm.get("language"); as language) { +
    + + + + - @if (language | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    - } @if (profileForm.get("languageLevel"); as languageLevel) { -
    - - - - + @if (language | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } @if (profileForm.get("languageLevel"); as languageLevel) { +
    + + + + - @if (languageLevel | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    + @if (languageLevel | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    } -
    - Количество добавляемых языков не более 4-х +
    + }
    + количество добавляемых языков не более 4-х + - Добавить язык + добавить язык -
    -
    - @if(languageItems().length || userLanguages.length){ @for (languageItem of - userLanguages.value; track $index) { -
    -
    -

    - {{ languageItem.language }} {{ languageItem.languageLevel }} -

    - -
    - - - -
    - } } -
    -
    - } +
    + @if(languageItems().length || userLanguages.length){ @for (languageItem of + userLanguages.value; track $index) { +
    +
    +

    + {{ languageItem.language }} +

    - +
    +
    + +
    - -
    -
    - - Сохранить + } @else if (editingStep === 'settings') { +
    + удалить профиль +
    + }
    @@ -904,7 +897,7 @@

    {{ isModalErrorSkillChooseText() }}.

    } @else {

    - Для публикации профиля, нужно заполнить все обязательные поля (они будут + для публикации профиля, нужно заполнить все обязательные поля (они будут подсвечены красным).

    @@ -912,9 +905,29 @@
    - + +
    +
    +

    подтвердите удаление аккаунта

    + +
    + + удалить аккаунт +
    +
    + +

    } @else if (editingStep === "contacts") { } @else if (editingStep === "achievements") { 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 899d5ace9..25e7e558f 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 @@ -8,7 +8,7 @@ import { OnInit, signal, } from "@angular/core"; -import { FormArray, FormGroup, ReactiveFormsModule } from "@angular/forms"; +import { Form, FormArray, FormGroup, ReactiveFormsModule } from "@angular/forms"; import { ActivatedRoute, Router, RouterModule } from "@angular/router"; import { ErrorMessage } from "@error/models/error-message"; import { Invite } from "@models/invite.model"; @@ -23,7 +23,7 @@ import { ProjectService } from "@services/project.service"; import { ButtonComponent, IconComponent, SelectComponent } from "@ui/components"; import { ModalComponent } from "@ui/components/modal/modal.component"; import { ValidationService } from "projects/core"; -import { Subscription, distinctUntilChanged, map, switchMap } from "rxjs"; +import { Subscription, distinctUntilChanged, forkJoin, map, switchMap } from "rxjs"; import { CommonModule, AsyncPipe } from "@angular/common"; import { ProjectNavigationComponent } from "./shared/project-navigation/project-navigation.component"; import { EditStep, ProjectStepService } from "./services/project-step.service"; @@ -41,6 +41,12 @@ import { ProjectAchievementsService } from "./services/project-achievements.serv import { Goal, GoalPostForm } 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 { 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"; /** * Компонент редактирования проекта @@ -92,6 +98,8 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { private readonly projectTeamService: ProjectTeamService, private readonly projectAchievementsService: ProjectAchievementsService, private readonly projectGoalsService: ProjectGoalService, + private readonly projectPartnerService: ProjectPartnerService, + private readonly projectResourceService: ProjectResourceService, private readonly snackBarService: SnackbarService, private readonly skillsService: SkillsService, private readonly projectAdditionalService: ProjectAdditionalService, @@ -158,11 +166,12 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { return this.projectGoalsService.goals; } - /** - * Проверяет, есть ли цели для отображения - */ - get hasGoals(): boolean { - return this.goals.length > 0; + get partners(): FormArray { + return this.projectPartnerService.partners; + } + + get resources(): FormArray { + return this.projectResourceService.resources; } ngOnInit(): void { @@ -388,10 +397,17 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { } this.setProjFormIsSubmitting(true); - this.projectService .updateProject(projectId, payload) - .pipe(switchMap(() => this.saveOrEditGoals(projectId))) + .pipe( + switchMap(() => + forkJoin({ + goals: this.saveOrEditGoals(projectId), + partners: this.projectPartnerService.savePartners(projectId), + resources: this.saveOrEditResources(projectId), + }) + ) + ) .subscribe({ next: () => { this.completeSubmitedProjectForm(projectId); @@ -421,15 +437,24 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { this.isAssignProjectToProgramModalOpen.set(false); } - public saveOrEditGoals(projectId: number) { + private saveOrEditGoals(projectId: number) { const goals = this.goals.value; - const hasExistingGoals = goals.some((g: GoalPostForm) => g.id); + const hasExistingGoals = goals.some((g: Partner) => g.id); return hasExistingGoals ? this.projectGoalService.editGoals(projectId) : this.projectGoalService.saveGoals(projectId); } + private saveOrEditResources(projectId: number) { + const resources = this.resources.value; + const hasExistingResources = resources.some((r: any) => r.id != null); + + return hasExistingResources + ? this.projectResourceService.editResources(projectId) + : this.projectResourceService.saveResources(projectId); + } + private completeSubmitedProjectForm(projectId: number) { this.snackBarService.success("данные успешно сохранены"); this.setProjFormIsSubmitting(false); @@ -566,26 +591,36 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { private loadProgramTagsAndProject(): void { this.route.data .pipe(map(d => d["data"])) - .subscribe(([project, goals, invites]: [Project, Goal[], Invite[]]) => { - // Используем сервис для инициализации данных проекта - this.projectFormService.initializeProjectData(project); - this.projectGoalService.initializeGoalsFromProject(goals); - this.projectTeamService.setInvites(invites); - this.projectTeamService.setCollaborators(project.collaborators); - - if (project.partnerProgram) { - this.isCompetitive = project.partnerProgram.canSubmit; - - this.projectAdditionalService.initializeAdditionalForm( - project.partnerProgram?.programFields, - project.partnerProgram?.programFieldValues - ); - } + .subscribe( + ([project, goals, partners, resources, invites]: [ + Project, + Goal[], + Partner[], + Resource[], + Invite[] + ]) => { + // Используем сервис для инициализации данных проекта + this.projectFormService.initializeProjectData(project); + this.projectGoalService.initializeGoalsFromProject(goals); + this.projectPartnerService.initializePartnerFromProject(partners); + this.projectResourceService.initializeResourcesFromProject(resources); + this.projectTeamService.setInvites(invites); + this.projectTeamService.setCollaborators(project.collaborators); + + if (project.partnerProgram) { + this.isCompetitive = project.partnerProgram.canSubmit; + + this.projectAdditionalService.initializeAdditionalForm( + project.partnerProgram?.programFields, + project.partnerProgram?.programFieldValues + ); + } - this.projectVacancyService.setVacancies(project.vacancies); - this.projectTeamService.setInvites(invites); + this.projectVacancyService.setVacancies(project.vacancies); + this.projectTeamService.setInvites(invites); - this.cdRef.detectChanges(); - }); + this.cdRef.detectChanges(); + } + ); } } diff --git a/projects/social_platform/src/app/office/projects/edit/edit.resolver.ts b/projects/social_platform/src/app/office/projects/edit/edit.resolver.ts index c28ca0769..3d48dcba9 100644 --- a/projects/social_platform/src/app/office/projects/edit/edit.resolver.ts +++ b/projects/social_platform/src/app/office/projects/edit/edit.resolver.ts @@ -8,6 +8,8 @@ import { Project } from "@models/project.model"; import { InviteService } from "@services/invite.service"; import { Invite } from "@models/invite.model"; import { Goal } from "@office/models/goals.model"; +import { Partner } from "@office/models/partner.model"; +import { Resource } from "@office/models/resource.model"; /** * Resolver для загрузки данных редактирования проекта @@ -31,7 +33,7 @@ import { Goal } from "@office/models/goals.model"; * Применяет forkJoin для параллельной загрузки данных проекта и приглашений, * что оптимизирует время загрузки страницы. */ -export const ProjectEditResolver: ResolveFn<[Project, Goal[], Invite[]]> = ( +export const ProjectEditResolver: ResolveFn<[Project, Goal[], Partner[], Resource[], Invite[]]> = ( route: ActivatedRouteSnapshot ) => { const projectService = inject(ProjectService); @@ -39,9 +41,11 @@ export const ProjectEditResolver: ResolveFn<[Project, Goal[], Invite[]]> = ( const projectId = Number(route.paramMap.get("projectId")); - return forkJoin<[Project, Goal[], Invite[]]>([ + return forkJoin<[Project, Goal[], Partner[], Resource[], Invite[]]>([ projectService.getOne(projectId), projectService.getGoals(projectId), + projectService.getPartners(projectId), + projectService.getResources(projectId), inviteService.getByProject(projectId), ]); }; diff --git a/projects/social_platform/src/app/office/projects/edit/services/project-goals.service.ts b/projects/social_platform/src/app/office/projects/edit/services/project-goals.service.ts index 863247232..5250b9636 100644 --- a/projects/social_platform/src/app/office/projects/edit/services/project-goals.service.ts +++ b/projects/social_platform/src/app/office/projects/edit/services/project-goals.service.ts @@ -298,7 +298,7 @@ export class ProjectGoalService { public saveGoals(projectId: number) { const goals = this.getGoalsData(); - return this.projectService.postGoals(projectId, goals).pipe( + return this.projectService.addGoals(projectId, goals).pipe( tap(results => { if (Array.isArray(results)) { results.forEach((createdGoal: any, idx: number) => { diff --git a/projects/social_platform/src/app/office/projects/edit/services/project-partner.service.ts b/projects/social_platform/src/app/office/projects/edit/services/project-partner.service.ts index 79f4d3e87..d94183e54 100644 --- a/projects/social_platform/src/app/office/projects/edit/services/project-partner.service.ts +++ b/projects/social_platform/src/app/office/projects/edit/services/project-partner.service.ts @@ -2,6 +2,9 @@ import { inject, Injectable, signal } from "@angular/core"; import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from "@angular/forms"; +import { Partner, PartnerPostForm } from "@office/models/partner.model"; +import { ProjectService } from "@office/services/project.service"; +import { catchError, forkJoin, map, Observable, of, tap } from "rxjs"; @Injectable({ providedIn: "root", @@ -9,6 +12,7 @@ import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from "@ang export class ProjectPartnerService { private readonly fb = inject(FormBuilder); private partnerForm!: FormGroup; + private readonly projectService = inject(ProjectService); public readonly partnerItems = signal([]); /** Флаг инициализации сервиса */ @@ -21,10 +25,10 @@ export class ProjectPartnerService { private initializePartnerForm(): void { this.partnerForm = this.fb.group({ partners: this.fb.array([]), - partnerName: [null], - partnerINN: [null], - partnerMention: [null, Validators.maxLength(200)], - partnerProfileLink: [null], + name: [null], + inn: [null, [Validators.minLength(10), Validators.maxLength(10)]], + contribution: [null, Validators.maxLength(200)], + decisionMaker: [null], }); } @@ -56,7 +60,7 @@ export class ProjectPartnerService { * Инициализирует партнера из данных проекта * Заполняет FormArray целей данными из проекта */ - public initializePartnerFromProject(partners: any[]): void { + public initializePartnerFromProject(partners: Partner[]): void { const partnerFormArray = this.partners; while (partnerFormArray.length !== 0) { @@ -65,7 +69,17 @@ export class ProjectPartnerService { if (partners && Array.isArray(partners)) { partners.forEach(partner => { - const partnerGroup = this.fb.group({}); + const partnerGroup = this.fb.group({ + id: [partner.id], + name: [partner.company.name, Validators.required], + inn: [partner.company.inn, Validators.required], + contribution: [partner.contribution, Validators.required], + company: [partner.company], + decisionMaker: [ + "https://app.procollab.ru/office/profile/" + partner.decisionMaker, + Validators.required, + ], + }); partnerFormArray.push(partnerGroup); }); @@ -91,49 +105,49 @@ export class ProjectPartnerService { } public get partnerName(): FormControl { - return this.partnerForm.get("partnerName") as FormControl; + return this.partnerForm.get("name") as FormControl; } public get partnerINN(): FormControl { - return this.partnerForm.get("partnerINN") as FormControl; + return this.partnerForm.get("inn") as FormControl; } public get partnerMention(): FormControl { - return this.partnerForm.get("partnerMention") as FormControl; + return this.partnerForm.get("contribution") as FormControl; } public get partnerProfileLink(): FormControl { - return this.partnerForm.get("partnerProfileLink") as FormControl; + return this.partnerForm.get("decisionMaker") as FormControl; } /** * Добавляет нового партнера или сохраняет изменения существующей. - * @param partnerName - название партнера (опционально) - * @param partnerINN - инн (опционально) - * @param partnerMention - вклад партнера (опционально) - * @param partnerProfileLink - ссылка на профиль представителя компании (опционально) + * @param name - название партнера (опционально) + * @param inn - инн (опционально) + * @param contribution - вклад партнера (опционально) + * @param decisionMaker - ссылка на профиль представителя компании (опционально) */ public addPartner( - partnerName?: string, - partnerINN?: string, - partnerMention?: string, - partnerProfileLink?: string + name?: string, + inn?: string, + contribution?: string, + decisionMaker?: string ): void { const partnerFormArray = this.partners; this.initializePartnerItems(partnerFormArray); - const name = partnerName || this.partnerForm.get("partnerName")?.value; - const INN = partnerINN || this.partnerForm.get("partnerINN")?.value; - const mention = partnerMention || this.partnerForm.get("partnerMention")?.value; - const profileLink = partnerProfileLink || this.partnerForm.get("partnerProfileLink")?.value; + const partnerName = name || this.partnerForm.get("name")?.value; + const INN = inn || this.partnerForm.get("inn")?.value; + const mention = contribution || this.partnerForm.get("contribution")?.value; + const profileLink = decisionMaker || this.partnerForm.get("decisionMaker")?.value; if ( - !name || + !partnerName || !INN || !mention || !profileLink || - name.trim().length === 0 || + partnerName.trim().length === 0 || mention.trim().length === 0 || INN.trim().length === 0 || profileLink.trim().length === 0 @@ -142,10 +156,11 @@ export class ProjectPartnerService { } const partnerItem = this.fb.group({ - partnerName: [name.trim(), Validators.required], - partnerINN: [INN.trim(), Validators.required], - partnerMention: [mention, Validators.required], - partnerProfileLink: [profileLink, Validators.required], + id: [null], + name: [partnerName.trim(), Validators.required], + inn: [INN.trim(), Validators.required], + contribution: [mention, Validators.required], + decisionMaker: [profileLink, Validators.required], }); this.partnerItems.update(items => [...items, partnerItem.value]); @@ -177,4 +192,72 @@ export class ProjectPartnerService { } }); } + + /** + * Получает данные всех партнеров для отправки на сервер + * @returns массив объектов партнеров + */ + public getPartnersData(): any[] { + return this.partners.value.map((partner: any) => ({ + id: partner.id ?? null, + name: partner.name, + inn: partner.inn, + contribution: partner.contribution, + decisionMaker: partner.decisionMaker, + })); + } + + /** + * Сохраняет только новых партнеров (у которых id === null) — отправляет POST. + * После ответов присваивает полученные id в соответствующие FormGroup. + * Возвращает Observable массива результатов (в порядке отправки). + */ + public savePartners(projectId: number) { + const partners = this.getPartnersData(); + + if (partners.length === 0) { + return of([]); + } + + const requests = partners.map(partner => { + const decisionMaker = Number(partner.decisionMaker.split("/").at(-1)); + + const payload: PartnerPostForm = { + name: partner.name, + inn: partner.inn, + contribution: partner.contribution, + decisionMaker, + }; + + return this.projectService.addPartner(projectId, payload).pipe( + map((res: any) => ({ res, idx: partner.id })), + catchError(err => of({ __error: true, err, original: partner })) + ); + }); + + return forkJoin(requests).pipe( + tap(results => { + results.forEach((r: any) => { + if (r && r.__error) { + console.error("Failed to post partner", r.err, "original:", r.original); + return; + } + + const created = r.res; + const idx = r.idx; + + if (created && created.id !== undefined && created.id !== null) { + const formGroup = this.partners.at(idx); + if (formGroup) { + formGroup.get("id")?.setValue(created.id); + } + } else { + console.warn("addPartner response has no id field:", r.res); + } + }); + + this.syncPartnerItems(this.partners); + }) + ); + } } diff --git a/projects/social_platform/src/app/office/projects/edit/services/project-resources.service.ts b/projects/social_platform/src/app/office/projects/edit/services/project-resources.service.ts index 51b0c04e7..85da03b03 100644 --- a/projects/social_platform/src/app/office/projects/edit/services/project-resources.service.ts +++ b/projects/social_platform/src/app/office/projects/edit/services/project-resources.service.ts @@ -2,12 +2,16 @@ import { inject, Injectable, signal } from "@angular/core"; import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from "@angular/forms"; +import { Resource, ResourcePostForm } from "@office/models/resource.model"; +import { ProjectService } from "@office/services/project.service"; +import { catchError, forkJoin, map, Observable, of, tap } from "rxjs"; @Injectable({ providedIn: "root", }) export class ProjectResourceService { private readonly fb = inject(FormBuilder); + private readonly projectService = inject(ProjectService); private resourceForm!: FormGroup; public readonly resourceItems = signal([]); @@ -21,9 +25,9 @@ export class ProjectResourceService { private initializeResourceForm(): void { this.resourceForm = this.fb.group({ resources: this.fb.array([]), - resoruceType: [null], - resoruceDescription: [null, Validators.maxLength(200)], - resourcePartner: [null], + type: [null], + description: [null, Validators.maxLength(200)], + partnerCompany: [null], }); } @@ -55,7 +59,7 @@ export class ProjectResourceService { * Инициализирует ресурсы из данных проекта * Заполняет FormArray целей данными из проекта */ - public initializeResourcesFromProject(resources: any[]): void { + public initializeResourcesFromProject(resources: Resource[]): void { const resourcesFormArray = this.resources; while (resourcesFormArray.length !== 0) { @@ -64,7 +68,12 @@ export class ProjectResourceService { if (resources && Array.isArray(resources)) { resources.forEach(resource => { - const partnerGroup = this.fb.group({}); + const partnerGroup = this.fb.group({ + id: [resource.id ?? null], + type: [resource.type, Validators.required], + description: [resource.description, Validators.required], + partnerCompany: [resource.partnerCompany, Validators.required], + }); resourcesFormArray.push(partnerGroup); }); @@ -90,51 +99,48 @@ export class ProjectResourceService { } public get resoruceType(): FormControl { - return this.resourceForm.get("resoruceType") as FormControl; + return this.resourceForm.get("type") as FormControl; } public get resoruceDescription(): FormControl { - return this.resourceForm.get("resoruceDescription") as FormControl; + return this.resourceForm.get("description") as FormControl; } public get resourcePartner(): FormControl { - return this.resourceForm.get("resourcePartner") as FormControl; + return this.resourceForm.get("partnerCompany") as FormControl; } /** * Добавляет нового ресурса или сохраняет изменения существующей. - * @param resoruceType - тип ресурса (опционально) - * @param resoruceDescription - описание ресурса (опционально) - * @param resourcePartner - ссылка на партнера (опционально) + * @param type - тип ресурса (опционально) + * @param description - описание ресурса (опционально) + * @param partnerCompany - ссылка на партнера (опционально) */ - public addPResource( - resoruceType?: string, - resoruceDescription?: string, - resourcePartner?: string - ): void { + public addResource(type?: string, description?: string, partnerCompany?: string): void { const resourcesFormArray = this.resources; this.initializePartnerItems(resourcesFormArray); - const type = resoruceType || this.resourceForm.get("resoruceType")?.value; - const description = resoruceDescription || this.resourceForm.get("resoruceDescription")?.value; - const partner = resourcePartner || this.resourceForm.get("resourcePartner")?.value; + const resourceType = type || this.resourceForm.get("type")?.value; + const resourceDescription = description || this.resourceForm.get("description")?.value; + const partner = partnerCompany || this.resourceForm.get("partnerCompany")?.value; if ( - !type || - !description || + !resourceType || + !resourceDescription || !partner || - type.trim().length === 0 || - description.trim().length === 0 || + resourceType.trim().length === 0 || + resourceDescription.trim().length === 0 || partner.trim().length === 0 ) { return; } const resourceItem = this.fb.group({ - resoruceType: [type.trim(), Validators.required], - resoruceDescription: [description.trim(), Validators.required], - resourcePartner: [partner, Validators.required], + id: [null], + type: [resourceType.trim(), Validators.required], + description: [resourceDescription.trim(), Validators.required], + partnerCompany: [partner, Validators.required], }); this.resourceItems.update(items => [...items, resourceItem.value]); @@ -166,4 +172,108 @@ export class ProjectResourceService { } }); } + + /** + * Получает данные все ресурсы для отправки на сервер + * @returns массив объектов ресурсов + */ + public getResourcesData(): any[] { + return this.resources.value.map((resource: any) => ({ + id: resource.id ?? null, + type: resource.type, + description: resource.description, + partnerCompany: resource.partnerCompany, + })); + } + + /** + * Сохраняет только новых ресурсов (у которых id === null) — отправляет POST. + * После ответов присваивает полученные id в соответствующие FormGroup. + * Возвращает Observable массива результатов (в порядке отправки). + */ + public saveResources(projectId: number) { + const resources = this.getResourcesData(); + + const requests = resources.map(resource => { + const payload: Omit = { + type: resource.type, + description: resource.description, + partnerCompany: resource.partnerCompany ?? "запрос к рынку", + }; + + return this.projectService.addResource(projectId, payload).pipe( + tap(() => console.log(payload)), + map((res: any) => ({ res, idx: resource.idx })), + catchError(err => of({ __error: true, err, original: resource })) + ); + }); + + return forkJoin(requests).pipe( + tap(results => { + results.forEach((r: any) => { + if (r && r.__error) { + console.error("Failed to post resource", r.err, "original:", r.original); + return; + } + + const created = r.res; + const idx = r.idx; + + if (created && created.id !== undefined && created.id !== null) { + const formGroup = this.resources.at(idx); + if (formGroup) { + formGroup.get("id")?.setValue(created.id); + } + } else { + console.warn("addResource response has no id field:", r.res); + } + }); + + this.syncResourceItems(this.resources); + }) + ); + } + + public editResources(projectId: number) { + const resources = this.getResourcesData(); + console.log(resources); + + const requests = resources.map(resource => { + const payload: Omit = { + type: resource.type, + description: resource.description, + partnerCompany: resource.partnerCompany ?? "запрос к рынку", + }; + + return this.projectService.editResource(projectId, resource.id, payload).pipe( + map((res: any) => ({ res })), + catchError(err => of({ __error: true, err, original: resource })) + ); + }); + + return forkJoin(requests).pipe( + tap(results => { + results.forEach((r: any) => { + if (r && r.__error) { + console.error("Failed to add resource", r.err, "original:", r.original); + return; + } + + const created = r.res; + const idx = r.idx; + + if (created && created.id !== undefined && created.id !== null) { + const formGroup = this.resources.at(idx); + if (formGroup) { + formGroup.get("id")?.setValue(created.id); + } + } else { + console.warn("addResource response has no id field:", r.res); + } + }); + + this.syncResourceItems(this.resources); + }) + ); + } } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.html b/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.html index 8232b4983..f249d6f42 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.html +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.html @@ -8,31 +8,31 @@
  • - @if (partners.at(i)?.get("partnerName"); as partnerName) { + @if (partners.at(i)?.get("name"); as name) {
    - @if (partnerName | controlError: "required") { + @if (name | controlError: "required") {
    {{ errorMessage.VALIDATION_REQUIRED }}
    }
    - } @if (partners.at(i)?.get("partnerINN"); as partnerINN) { + } @if (partners.at(i)?.get("inn"); as inn) {
    - @if (partnerINN | controlError: "required") { + @if (inn | controlError: "required") {
    {{ errorMessage.VALIDATION_REQUIRED }}
    @@ -41,33 +41,33 @@ }
    - @if (partners.at(i)?.get("partnerMention"); as partnerMention) { + @if (partners.at(i)?.get("contribution"); as contribution) {
    - @if (partnerINN | controlError: "required") { + @if (contribution | controlError: "required") {
    {{ errorMessage.VALIDATION_REQUIRED }}
    }
    - } @if (partners.at(i)?.get("partnerProfileLink"); as partnerProfileLink) { + } @if (partners.at(i)?.get("decisionMaker"); as decisionMaker) {
    - @if (partnerProfileLink | controlError: "required") { + @if (decisionMaker | controlError: "required") {
    {{ errorMessage.VALIDATION_REQUIRED }}
    @@ -107,46 +107,48 @@ @for (control of resources.controls; track i; let i = $index) {
  • - @if (resources.at(i)?.get("resoruceType"); as resoruceType) { + @if (resources.at(i)?.get("type"); as type) {
    - @if (resoruceType | controlError: "required") { + @if (type | controlError: "required") {
    {{ errorMessage.VALIDATION_REQUIRED }}
    }
    - } @if (resources.at(i)?.get("resoruceDescription"); as resoruceDescription) { + } @if (resources.at(i)?.get("description"); as description) {
    - @if (resoruceDescription | controlError: "required") { + @if (description | controlError: "required") {
    {{ errorMessage.VALIDATION_REQUIRED }}
    }
    - } @if (resources.at(i)?.get("resourcePartner"); as resourcePartner) { + } @if (resources.at(i)?.get("partnerCompany"); as partnerCompany) {
    - @if (resourcePartner | controlError: "required") { + @if (partnerCompany | controlError: "required") {
    {{ errorMessage.VALIDATION_REQUIRED }}
    diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.ts b/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.ts index 976f7d2cd..dbdd57b5b 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.ts +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.ts @@ -1,7 +1,7 @@ /** @format */ import { CommonModule } from "@angular/common"; -import { Component, inject, OnDestroy } from "@angular/core"; +import { Component, inject, Input, OnDestroy } from "@angular/core"; import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms"; import { ErrorMessage } from "@error/models/error-message"; import { IconComponent } from "@uilib"; @@ -11,6 +11,9 @@ import { ButtonComponent, InputComponent, SelectComponent } from "@ui/components import { Subscription } from "rxjs"; import { ControlErrorPipe } from "@corelib"; import { TextareaComponent } from "@ui/components/textarea/textarea.component"; +import { ProjectService } from "@office/services/project.service"; +import { generateOptionsList, optionsListElement } from "@utils/generate-options-list"; +import { Partner } from "@office/models/partner.model"; @Component({ selector: "app-project-partner-resources-step", @@ -29,8 +32,11 @@ import { TextareaComponent } from "@ui/components/textarea/textarea.component"; ], }) export class ProjectPartnerResourcesStepComponent implements OnDestroy { + @Input() projectId!: number; + private readonly projectPartnerService = inject(ProjectPartnerService); private readonly projectResourceService = inject(ProjectResourceService); + private readonly projectService = inject(ProjectService); private readonly fb = inject(FormBuilder); readonly errorMessage = ErrorMessage; @@ -54,15 +60,15 @@ export class ProjectPartnerResourcesStepComponent implements OnDestroy { return this.projectResourceService.resources; } - get resoruceType() { + get type() { return this.projectResourceService.resoruceType; } - get resoruceDescription() { + get description() { return this.projectResourceService.resoruceDescription; } - get resourcePartner() { + get partnerCompany() { return this.projectResourceService.resourcePartner; } @@ -70,19 +76,19 @@ export class ProjectPartnerResourcesStepComponent implements OnDestroy { return this.projectPartnerService.partners; } - get partnerName() { + get name() { return this.projectPartnerService.partnerName; } - get partnerINN() { + get inn() { return this.projectPartnerService.partnerINN; } - get partnerMention() { + get contribution() { return this.projectPartnerService.partnerMention; } - get partnerProfileLink() { + get decisionMaker() { return this.projectPartnerService.partnerProfileLink; } @@ -94,30 +100,66 @@ export class ProjectPartnerResourcesStepComponent implements OnDestroy { return this.resources.length > 0; } + get resourcesCompanyOptions(): optionsListElement[] { + const partners = this.partners.value || []; + + const partnerOptions: optionsListElement[] = partners.map((partner: any, index: number) => ({ + id: partner.company.id ?? index, + value: partner.id ?? null, + label: partner.name, + })); + + partnerOptions.push({ + id: -1, + value: "запрос к рынку", + label: "запрос к рынку", + }); + + return partnerOptions; + } + + get resourcesTypeOptions(): optionsListElement[] { + const resourceOptions = [ + { + id: 1, + value: "infrastructure", + label: "Инфраструктурный", + }, + { + id: 2, + value: "staff", + label: "Кадровый", + }, + { + id: 3, + value: "financial", + label: "Финансовый", + }, + { + id: 4, + value: "information", + label: "Информационный", + }, + ]; + + return resourceOptions; + } + /** * Добавление партнера */ - addPartner( - partnerName?: string, - partnerINN?: string, - partnerMention?: string, - partnerProfileLink?: string - ): void { + addPartner(name?: string, inn?: string, contribution?: string, decisionMaker?: string): void { this.partners.push( this.fb.group({ - partnerName: [partnerName, [Validators.required]], - partnerINN: [partnerINN, [Validators.required]], - partnerMention: [partnerMention, [Validators.required]], - partnerProfileLink: [partnerProfileLink, Validators.required], + id: [null], + name: [name, [Validators.required]], + inn: [inn, [Validators.required]], + contribution: [contribution, [Validators.required]], + decisionMaker: [decisionMaker, Validators.required], }) ); - this.projectPartnerService.addPartner( - partnerName, - partnerINN, - partnerMention, - partnerProfileLink - ); + this.projectPartnerService.addPartner(name, inn, contribution, decisionMaker); } /** @@ -126,30 +168,30 @@ export class ProjectPartnerResourcesStepComponent implements OnDestroy { */ removePartner(index: number, partnersId: number) { this.projectPartnerService.removePartner(index); - // TODO: ручка на удаление партнера + this.projectService.deletePartner(this.projectId, partnersId).subscribe(); } /** * Добавление ресурса */ - addResource(resoruceType?: string, resoruceDescription?: string, resourcePartner?: string): void { + addResource(type?: string, description?: string, partnerCompany?: string): void { this.resources.push( this.fb.group({ - resoruceType: [resoruceType, [Validators.required]], - resoruceDescription: [resoruceDescription, [Validators.required]], - resourcePartner: [resourcePartner, [Validators.required]], + type: [type, [Validators.required]], + description: [description, [Validators.required]], + partnerCompany: [partnerCompany, [Validators.required]], }) ); - this.projectResourceService.addPResource(resoruceType, resoruceDescription, resourcePartner); + this.projectResourceService.addResource(type, description, partnerCompany); } /** * Удаление ресурса * @param index - индекс ресурса */ - removeResource(index: number, resource: number) { + removeResource(index: number, resourceId: number) { this.projectResourceService.removeResource(index); - // TODO: ручка на удаление ресурса + this.projectService.deleteResource(this.projectId, resourceId).subscribe(); } } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.ts b/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.ts index 7c502dbe4..40fc582ac 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.ts +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.ts @@ -135,7 +135,7 @@ export class ProjectVacancyStepComponent implements OnInit { } /** - * + * Отображение блока вакансий */ createVacancyBlock(): void { this.showFields = true; diff --git a/projects/social_platform/src/app/office/services/project.service.ts b/projects/social_platform/src/app/office/services/project.service.ts index 27a8106be..c4be7039e 100644 --- a/projects/social_platform/src/app/office/services/project.service.ts +++ b/projects/social_platform/src/app/office/services/project.service.ts @@ -11,6 +11,8 @@ import { Collaborator } from "@office/models/collaborator.model"; import { ProjectAssign } from "@office/projects/models/project-assign.model"; import { projectNewAdditionalProgramVields } from "@office/models/partner-program-fields.model"; import { Goal, GoalPostForm } from "@office/models/goals.model"; +import { Partner, PartnerPostForm } from "@office/models/partner.model"; +import { Resource, ResourcePostForm } from "@office/models/resource.model"; /** * Сервис для управления проектами @@ -70,6 +72,109 @@ export class ProjectService { return this.apiService.get>(`${this.AUTH_USERS_URL}/projects/`, params); } + /** + * + * @param projectId + * @param params + * @returns Создать или привязать компанию к проекту. + * Если компания с таким ИНН уже существует — создаёт или обновляет связь ProjectCompany. + * Если компании нет — создаёт новую и тут же привязывает. + */ + addPartner(projectId: number, params: PartnerPostForm) { + return this.apiService.post(`${this.PROJECTS_URL}/${projectId}/companies/`, params); + } + + /** + * Получить список всех компаний-партнёров (связей ProjectCompany) конкретного проекта. + * + * @param projectId + * + * @returns данные компании + * @returns вклад + * @returns ответственного + */ + getPartners(projectId: number): Observable { + return this.apiService.get(`${this.PROJECTS_URL}/${projectId}/companies/list/`); + } + + /** + * @param projectId + * @param companyId + * + * @returns Обновить информацию о связи проекта с компанией. + * Можно изменить вклад (contribution) и/или ответственное лицо (decision_maker). + * Компания остаётся без изменений. + */ + editParter( + projectId: number, + companyId: number, + params: Pick + ) { + return this.apiService.patch( + `${this.PROJECTS_URL}/${projectId}/companies/${companyId}/`, + params + ); + } + + /** + * @param projectId + * @param companyId + * + * @returns Удалить связь проекта с компанией. Компания в базе остаётся, удаляется только запись ProjectCompany. + */ + deletePartner(projectId: number, companyId: number) { + return this.apiService.delete(`${this.PROJECTS_URL}/${projectId}/companies/${companyId}/`); + } + + /** + * + * @param projectId + * @param params + * @returns Создать новый ресурс в проекте. + * Если partner_company указана, проверяется, что она действительно является партнёром данного проекта. + */ + addResource(projectId: number, params: Omit) { + return this.apiService.post(`${this.PROJECTS_URL}/${projectId}/resources/`, { + project_id: projectId, + ...params, + }); + } + + /** + * + * @param projectId + * @returns Получить список всех ресурсов проекта. + * Каждый ресурс содержит тип, описание и партнёра (если назначен) + */ + getResources(projectId: number): Observable { + return this.apiService.get(`${this.PROJECTS_URL}/${projectId}/resources/`); + } + + /** + * @param projectId + * @param resourceId + * + * @returns Полностью обновить данные ресурса. + * Используется, если нужно заменить все поля сразу. + */ + editResource(projectId: number, resourceId: number, params: Omit) { + return this.apiService.patch(`${this.PROJECTS_URL}/${projectId}/resources/${resourceId}/`, { + project_id: projectId, + ...params, + }); + } + + /** + * @param projectId + * @param resourceId + * + * @returns Удалить ресурс проекта. + * Удаляется только сам ресурс, проект и компании не затрагиваются. + */ + deleteResource(projectId: number, resourceId: number) { + return this.apiService.delete(`${this.PROJECTS_URL}/${projectId}/resources/${resourceId}/`); + } + /** * Получает список целей проекта * @@ -82,7 +187,7 @@ export class ProjectService { /** * Отправляем цель */ - postGoals(projectId: number, params: GoalPostForm[]) { + addGoals(projectId: number, params: GoalPostForm[]) { return this.apiService.post(`${this.PROJECTS_URL}/${projectId}/goals/`, params); } diff --git a/projects/social_platform/src/assets/icons/svg/efficiency.svg b/projects/social_platform/src/assets/icons/svg/efficiency.svg new file mode 100644 index 000000000..0524ab9d8 --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/efficiency.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/svg/pen.svg b/projects/social_platform/src/assets/icons/svg/pen.svg new file mode 100644 index 000000000..f2ecc3e11 --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/pen.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/svg/settings.svg b/projects/social_platform/src/assets/icons/svg/settings.svg index dfccbd57e..9d895043a 100644 --- a/projects/social_platform/src/assets/icons/svg/settings.svg +++ b/projects/social_platform/src/assets/icons/svg/settings.svg @@ -1,3 +1,4 @@ - - + + + diff --git a/projects/social_platform/src/assets/icons/svg/task.svg b/projects/social_platform/src/assets/icons/svg/task.svg new file mode 100644 index 000000000..c1d0a7167 --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/task.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg index 62deed68e..013d12458 100644 --- a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg +++ b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/projects/social_platform/src/styles/_global.scss b/projects/social_platform/src/styles/_global.scss index 541b91147..177a2932d 100644 --- a/projects/social_platform/src/styles/_global.scss +++ b/projects/social_platform/src/styles/_global.scss @@ -70,9 +70,10 @@ button { label.field-label { display: block; margin-bottom: 6px; + margin-left: 12px; color: var(--black); - @include body-14; + @include body-12; } .error { From af3240fc5ea6de1f08ac11c961e29d72d13af363 Mon Sep 17 00:00:00 2001 From: Awakich Date: Fri, 10 Oct 2025 19:50:25 +0300 Subject: [PATCH 100/126] add styles for partner & resources & work-section in detail project page --- .../office/profile/edit/edit.component.scss | 18 ++++++++++-------- .../work-section/work-section.component.scss | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.scss b/projects/social_platform/src/app/office/profile/edit/edit.component.scss index 70880f4d0..6a99652b0 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.scss +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.scss @@ -103,9 +103,9 @@ } &__file { + flex-grow: 1; min-width: 0; max-width: 333px; - flex-grow: 1; } &__slides-title { @@ -141,7 +141,6 @@ } } - .error__phone-number { margin-bottom: 10px; } @@ -160,14 +159,14 @@ &__row { display: flex; - align-items: center; gap: 20px; + align-items: center; } &__column { display: flex; - gap: 12px; flex-direction: column; + gap: 12px; &--date { display: grid; @@ -179,8 +178,8 @@ &__action { &--icons { display: flex; - align-items: center; gap: 10px; + align-items: center; } } } @@ -246,15 +245,17 @@ } } -.edit-icon, .basket-icon { - padding: 6px; +.edit-icon, +.basket-icon { width: 20px; height: 20px; + padding: 6px; border-radius: 50%; } .edit-icon { border: 0.5px solid var(--accent); + i { color: var(--accent) !important; } @@ -262,6 +263,7 @@ .basket-icon { border: 0.5px solid var(--red); + i { color: var(--red) !important; } @@ -349,7 +351,7 @@ &__delete-icon { display: flex; justify-content: center; - margin: 36px 0px; + margin: 36px 0; } &__title { diff --git a/projects/social_platform/src/app/office/projects/detail/work-section/work-section.component.scss b/projects/social_platform/src/app/office/projects/detail/work-section/work-section.component.scss index 776e037e7..f0220bcf1 100644 --- a/projects/social_platform/src/app/office/projects/detail/work-section/work-section.component.scss +++ b/projects/social_platform/src/app/office/projects/detail/work-section/work-section.component.scss @@ -1,4 +1,4 @@ -.work-section{ +.work-section { display: grid; grid-template-columns: 4fr 3fr 3fr; grid-gap: 20px; From eebe92e8b79feda73703ddc24d24d94ab57933ac Mon Sep 17 00:00:00 2001 From: Awakich Date: Wed, 15 Oct 2025 19:23:12 +0300 Subject: [PATCH 101/126] change design of project chat section, fix websockets --- .../message-input.component.html | 19 +++-- .../message-input.component.scss | 77 +++++++++++++------ .../message-input/message-input.component.ts | 10 ++- .../projects/detail/chat/chat.component.scss | 3 +- .../chat-message/chat-message.component.html | 24 +++--- .../chat-message/chat-message.component.scss | 14 ++-- 6 files changed, 92 insertions(+), 55 deletions(-) diff --git a/projects/social_platform/src/app/office/features/message-input/message-input.component.html b/projects/social_platform/src/app/office/features/message-input/message-input.component.html index a0c212326..08da963f1 100644 --- a/projects/social_platform/src/app/office/features/message-input/message-input.component.html +++ b/projects/social_platform/src/app/office/features/message-input/message-input.component.html @@ -5,11 +5,16 @@
      @for (file of attachFiles; let index = $index; track index) {
    • - -

      {{ file.name }}

      -
      {{ +file.size | formatedFileSize }}
      - + +
      +

      {{ file.name.split(".")[0] }}

      + @if (file.type) { +
      + {{ file.type.includes("/") ? (file.type | fileType) : (file.type | uppercase) }} • + {{ +file.size | formatedFileSize }} +
      + } +
      @if (file.loading) { {{ file.name }}
  • } @else { } diff --git a/projects/social_platform/src/app/office/features/message-input/message-input.component.scss b/projects/social_platform/src/app/office/features/message-input/message-input.component.scss index 9c1f3bbb4..c546b918b 100644 --- a/projects/social_platform/src/app/office/features/message-input/message-input.component.scss +++ b/projects/social_platform/src/app/office/features/message-input/message-input.component.scss @@ -6,6 +6,7 @@ $button-size: 40px; .message-input { + position: relative; padding: 12px; border: 0.5px solid var(--medium-grey-for-outline); border-radius: 8px; @@ -100,11 +101,26 @@ $button-size: 40px; } .files-list { + position: absolute; + bottom: 77%; + left: 15px; display: flex; - padding: 0 0 12px; list-style-type: none; &__item { + display: flex; + gap: 12px; + align-items: center; + justify-content: space-between; + padding: 24px; + background-color: var(--light-white); + border-color: var(--medium-grey-for-outline); + border-style: solid; + border-width: 0.5px 0.5px 0px 0.5px; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + box-shadow: 4px -4px 4px rgb(51 51 51 / 20%); + &:not(:last-child) { margin-right: 18px; } @@ -112,11 +128,6 @@ $button-size: 40px; } .file { - min-width: 140px; - padding: 10px; - border: 1px solid var(--medium-grey-for-outline); - border-radius: 20px; - &--loading { .file__name { color: var(--dark-grey); @@ -128,37 +139,53 @@ $button-size: 40px; } } - &__type { - display: block; - margin-bottom: 12px; + &__info { + display: flex; + flex-direction: column; + } + + &__icon { + display: flex; + align-items: center; + align-self: center; + justify-content: center; + width: 20px; + height: 20px; + margin-right: 6px; + cursor: pointer; + border-radius: var(--rounded-xl); + transition: color 0.2s; + + &--file { + color: var(--accent); + border: 0.5px solid var(--accent); + + &:hover { + color: var(--accent-dark); + } + } + + &--basket { + color: var(--red); + border: 0.5px solid var(--red); + + &:hover { + color: var(--red-dark); + } + } } &__name { color: var(--black); - - @include typography.body-14; } - &__size { - margin-bottom: 12px; + &__meta { color: var(--dark-grey); - - @include typography.body-12; } &__loading { color: var(--accent); } - - &__basket { - color: var(--red); - cursor: pointer; - transition: color 0.2s; - - &:hover { - color: var(--red-dark); - } - } } .drop-modal { diff --git a/projects/social_platform/src/app/office/features/message-input/message-input.component.ts b/projects/social_platform/src/app/office/features/message-input/message-input.component.ts index 44e6c650b..ccabd06e8 100644 --- a/projects/social_platform/src/app/office/features/message-input/message-input.component.ts +++ b/projects/social_platform/src/app/office/features/message-input/message-input.component.ts @@ -18,6 +18,7 @@ import { AutosizeModule } from "ngx-autosize"; import { NgxMaskModule } from "ngx-mask"; import { IconComponent } from "@ui/components"; import { FormatedFileSizePipe } from "@core/pipes/formatted-file-size.pipe"; +import { UpperCasePipe } from "@angular/common"; /** * Компонент ввода сообщений для чата @@ -43,7 +44,14 @@ import { FormatedFileSizePipe } from "@core/pipes/formatted-file-size.pipe"; }, ], standalone: true, - imports: [IconComponent, NgxMaskModule, AutosizeModule, FileTypePipe, FormatedFileSizePipe], + imports: [ + IconComponent, + NgxMaskModule, + AutosizeModule, + FileTypePipe, + FormatedFileSizePipe, + UpperCasePipe, + ], }) export class MessageInputComponent implements OnInit, OnDestroy, ControlValueAccessor { /** diff --git a/projects/social_platform/src/app/office/projects/detail/chat/chat.component.scss b/projects/social_platform/src/app/office/projects/detail/chat/chat.component.scss index c117bf012..77c6bba74 100644 --- a/projects/social_platform/src/app/office/projects/detail/chat/chat.component.scss +++ b/projects/social_platform/src/app/office/projects/detail/chat/chat.component.scss @@ -45,7 +45,6 @@ $input-height: 66px + 18px; .main { display: flex; flex-direction: column; - background-color: var(--light-white); &__chat { display: none; @@ -57,6 +56,8 @@ $input-height: 66px + 18px; @include responsive.apply-desktop { display: block; + background-color: var(--light-white); + border-radius: 15px; } } } diff --git a/projects/social_platform/src/app/ui/components/chat-message/chat-message.component.html b/projects/social_platform/src/app/ui/components/chat-message/chat-message.component.html index bdc558e2f..f870f6f6d 100644 --- a/projects/social_platform/src/app/ui/components/chat-message/chat-message.component.html +++ b/projects/social_platform/src/app/ui/components/chat-message/chat-message.component.html @@ -10,7 +10,7 @@
    -
    +
    {{ chatMessage.author.firstName }} {{ chatMessage.author.lastName }}
    -
    +
    {{ chatMessage.createdAt | dayjs: "format":"HH:mm" }}
    @if (chatMessage.replyTo) {
    -
    +
    {{ chatMessage.replyTo.author.firstName }} {{ chatMessage.replyTo.author.lastName }}
    -

    +

    {{ chatMessage.replyTo.text }}

    @@ -47,7 +47,7 @@ } -
    {{ chatMessage.text }}
    +
    {{ chatMessage.text }}
    - +
    @if (authService.profile | async; as profile) { @@ -65,13 +65,13 @@ [style.opacity]="isOpen ? 1 : 0" [style.pointer-events]="isOpen ? 'auto' : 'none'" > -
  • Скопировать
  • -
  • Ответить
  • +
  • скопировать
  • +
  • ответить
  • @if (profile.id === chatMessage.author.id) { -
  • Редактировать
  • -
  • - Удалить +
  • редактировать
  • +
  • + удалить
  • } diff --git a/projects/social_platform/src/app/ui/components/chat-message/chat-message.component.scss b/projects/social_platform/src/app/ui/components/chat-message/chat-message.component.scss index b7722b1af..d5b919b33 100644 --- a/projects/social_platform/src/app/ui/components/chat-message/chat-message.component.scss +++ b/projects/social_platform/src/app/ui/components/chat-message/chat-message.component.scss @@ -8,7 +8,7 @@ display: flex; align-items: center; max-width: 100%; - padding: 10px 28px 10px 20px; + padding: 10px; &--unread { background-color: #f7f3ff; @@ -37,6 +37,7 @@ &__top { display: flex; align-items: center; + justify-content: space-between; margin-bottom: 5px; } @@ -57,14 +58,14 @@ } &__content { - color: var(--dark-grey); + color: var(--black); word-break: break-word; white-space: pre-wrap; } &__name { margin-right: 15px; - color: var(--black); + color: var(--accent); } &__menu-dots { @@ -78,13 +79,8 @@ margin-left: auto; color: var(--dark-grey); cursor: pointer; - background-color: var(--white); border-radius: var(--rounded-md); transition: background-color 0.2s; - - &:hover { - background-color: var(--light-gray); - } } &__contextmenu { @@ -98,7 +94,7 @@ } .contextmenu { - padding: 8px; + padding: 10px; background-color: var(--white); border-radius: var(--rounded-lg); box-shadow: 5px 5px 25px rgb(159 159 159 / 15%); From 08568b67d668f47eef34e6e129cf21052256e99b Mon Sep 17 00:00:00 2001 From: Awakich Date: Wed, 15 Oct 2025 19:23:45 +0300 Subject: [PATCH 102/126] change styles for chat in project section --- .../office/features/message-input/message-input.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/social_platform/src/app/office/features/message-input/message-input.component.scss b/projects/social_platform/src/app/office/features/message-input/message-input.component.scss index c546b918b..f9f689b14 100644 --- a/projects/social_platform/src/app/office/features/message-input/message-input.component.scss +++ b/projects/social_platform/src/app/office/features/message-input/message-input.component.scss @@ -116,7 +116,7 @@ $button-size: 40px; background-color: var(--light-white); border-color: var(--medium-grey-for-outline); border-style: solid; - border-width: 0.5px 0.5px 0px 0.5px; + border-width: 0.5px 0.5px 0; border-top-left-radius: 10px; border-top-right-radius: 10px; box-shadow: 4px -4px 4px rgb(51 51 51 / 20%); From 2d42b00b55f41aad52088f5785bd52508550cbc1 Mon Sep 17 00:00:00 2001 From: Awakich Date: Wed, 15 Oct 2025 22:11:31 +0300 Subject: [PATCH 103/126] run format --- .../office/profile/edit/edit.component.html | 1495 +++++++++-------- 1 file changed, 748 insertions(+), 747 deletions(-) diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.html b/projects/social_platform/src/app/office/profile/edit/edit.component.html index 62bbb3291..b3fdee217 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.html +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.html @@ -2,394 +2,267 @@ @if (profileForm.get("userType"); as currentType) {
    -
    -
    - -

    редактирование профиля

    -
    +
    +
    + +

    редактирование профиля

    +
    -
    - + - cохранить + " + [loader]="profileFormSubmitting" + [ngStyle]="{ + opacity: + editEducationClick || + editWorkClick || + editLanguageClick || + isEducationDirty || + isWorkDirty || + isLanguageDirty + ? '0.6' + : '1', + cursor: + editEducationClick || + editWorkClick || + editLanguageClick || + isEducationDirty || + isWorkDirty || + isLanguageDirty + ? 'not-allowed' + : 'pointer' + }" + customTypographyClass="text-body-12" + (click)="saveProfile()" + > + cохранить +
    -
    -
    - -
    -
      - @for (item of navItems; track $index) { -
    • - -

      + +

      +
        + @for (item of navItems; track $index) { +
      • - {{ item.label }} -

        -
      • - } -
      -
      - -
      - @if(editingStep === 'main'){ -
      -
      - @if (profileForm.get("avatar"); as avatar) { -
      - - - @if (avatar | controlError: "required") { -
      - {{ errorMessage.EMPTY_AVATAR }} -
      - } -
      - } @if (profileForm.get("coverImageAddress"); as coverImageAddress) { -
      - +

      - - - -

      - обложка формата -
      - .JPG или .JPEG весом до 50МБ -

      - @if (coverImageAddress | controlError: "required") { -

      загрузите файл

      - } - - -
      - } -
      - -
      -
      - @if (profileForm.get("firstName"); as firstName) { -
      - - - @if (firstName | controlError: "required") { -
      - {{ errorMessage.VALIDATION_REQUIRED }} -
      - } -
      - } @if (profileForm.get("lastName"); as lastName) { -
      - - - @if (lastName | controlError: "required") { -
      - {{ errorMessage.VALIDATION_REQUIRED }} -
      - } -
      - } -
      - -
      - @if (profileForm.get("city"); as city) { -
      - - - @if (city | controlError: "required") { -
      - {{ errorMessage.VALIDATION_REQUIRED }} -
      - } -
      - } @if (profileForm.get("birthday"); as birthday) { -
      - - - @if (birthday | controlError: "required") { -
      - {{ errorMessage.VALIDATION_REQUIRED }} -
      - } -
      - } -
      - -
      - @if (profileForm.get("userType"); as userType) { @if (userType.value !== 1) { -
      - - @if (roles | async; as options) { - - } @if (userType | controlError: "required") { -
      - {{ errorMessage.VALIDATION_REQUIRED }} -
      - } -
      - } } @if (profileForm.get("speciality"); as speciality) { -
      - -
      - -
      - @if (speciality | controlError: "required") { -
      - {{ errorMessage.VALIDATION_REQUIRED }} -
      - } -
      - } -
      -
      - -
      - @if (profileForm.get("aboutMe"); as aboutMe) { -
      - - - @if (aboutMe | controlError: "required") { -
      - {{ errorMessage.VALIDATION_REQUIRED }} -
      - } -
      + {{ item.label }} +

      +
    • } -
    +
    - } @if (editingStep === 'education') { -
    -
    - @if (showEducationFields) { -
    - @if (profileForm.get("entryYear"); as entryYear) { -
    - - - - - @if (entryYear | controlError: "required") { +
    + @if(editingStep === 'main'){ +
    +
    + @if (profileForm.get("avatar"); as avatar) { +
    + + + @if (avatar | controlError: "required") {
    - {{ errorMessage.VALIDATION_REQUIRED }} + {{ errorMessage.EMPTY_AVATAR }}
    }
    - } @if (profileForm.get("completionYear"); as completionYear) { -
    - - + - - - @if (completionYear | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } + + + +

    + обложка формата +
    + .JPG или .JPEG весом до 50МБ +

    + @if (coverImageAddress | controlError: "required") { +

    загрузите файл

    + } +
    +
    }
    -
    - @if (profileForm.get("organizationName"); as organizationName) { -
    - - - @if (organizationName | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    +
    +
    + @if (profileForm.get("firstName"); as firstName) { +
    + + + @if (firstName | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } @if (profileForm.get("lastName"); as lastName) { +
    + + + @if (lastName | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    } -
    - } -
    +
    -
    - @if (profileForm.get("description"); as description) { -
    - - - @if (description | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    +
    + @if (profileForm.get("city"); as city) { +
    + + + @if (city | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } @if (profileForm.get("birthday"); as birthday) { +
    + + + @if (birthday | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    } -
    - } -
    +
    -
    - @if (profileForm.get("educationLevel"); as educationLevel) { -
    - - - - - @if (educationLevel | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    +
    + @if (profileForm.get("userType"); as userType) { @if (userType.value !== 1) { +
    + + @if (roles | async; as options) { + + } @if (userType | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } } @if (profileForm.get("speciality"); as speciality) { +
    + +
    + +
    + @if (speciality | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    } -
    - } +
    - @if (profileForm.get("educationStatus"); as educationStatus) { + @if (profileForm.get("aboutMe"); as aboutMe) {
    - - обо мне + - - - @if (educationStatus | controlError: "required") { + id="aboutMe" + [error]="aboutMe | controlError" + type="text" + placeholder="Я java developer, очень люблю котиков" + formControlName="aboutMe" + > + @if (aboutMe | controlError: "required") {
    {{ errorMessage.VALIDATION_REQUIRED }}
    @@ -397,109 +270,63 @@

    редактирование профиля

    }
    - } - - - добавить образование - -
    - -
    - @if(educationItems().length || education.length){ @for (educationItem of education.value; - track $index) { -
    -

    - {{ educationItem.organizationName }} -

    - -

    - @if(educationItem.entryYear && educationItem.completionYear) { - {{ educationItem.entryYear }} год • {{ educationItem.completionYear }} год } @else if - (educationItem.entryYear && !educationItem.completionYear) { - {{ educationItem.entryYear }} год } @else if (!educationItem.entryYear && - educationItem.completionYear){ {{ educationItem.completionYear }} год } -

    - -
    -
    -

    - {{ educationItem.description }} -

    - -

    - {{ educationItem.educationLevel }} -

    - -

    - {{ educationItem.educationStatus }} -

    -
    - -
    -
    - -
    - -
    - -
    -
    -
    -
    - } } -
    -
    - } @if (editingStep === 'experience') { -
    -
    - @if (showWorkFields){ + } @if (editingStep === 'education') { +
    + @if (showEducationFields) {
    - @if (profileForm.get("entryYearWork"); as entryYearWork) { + @if (profileForm.get("entryYear"); as entryYear) {
    - + - + - @if (entryYearWork | controlError: "required") { + @if (entryYear | controlError: "required") {
    {{ errorMessage.VALIDATION_REQUIRED }}
    }
    - } @if (profileForm.get("completionYearWork"); as completionYearWork) { + } @if (profileForm.get("completionYear"); as completionYear) {
    - + - + + @if (completionYear | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } +
    - @if (completionYearWork | controlError: "required") { +
    + @if (profileForm.get("organizationName"); as organizationName) { +
    + + + @if (organizationName | controlError: "required") {
    {{ errorMessage.VALIDATION_REQUIRED }}
    @@ -507,138 +334,311 @@

    редактирование профиля

    }
    -
    -
    - @if (profileForm.get("organization"); as organization) { -
    - - - @if (organization | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    +
    + @if (profileForm.get("description"); as description) { +
    + + + @if (description | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    } -
    - } -
    +
    -
    - @if (profileForm.get("jobPosition"); as jobPosition) { -
    - - - @if (jobPosition | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    +
    + @if (profileForm.get("educationLevel"); as educationLevel) { +
    + + + + + @if (educationLevel | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    } -
    - } -
    +
    -
    - @if (profileForm.get("descriptionWork"); as descriptionWork) { -
    - - - @if (descriptionWork | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    +
    + @if (profileForm.get("educationStatus"); as educationStatus) { +
    + + + + + @if (educationStatus | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    } -
    +
    } + + + добавить образование + +
    - } - - добавить работу - - +
    + @if(educationItems().length || education.length){ @for (educationItem of + education.value; track $index) { +
    +

    + {{ educationItem.organizationName }} +

    + +

    + @if(educationItem.entryYear && educationItem.completionYear) { + {{ educationItem.entryYear }} год • {{ educationItem.completionYear }} год } @else + if (educationItem.entryYear && !educationItem.completionYear) { + {{ educationItem.entryYear }} год } @else if (!educationItem.entryYear && + educationItem.completionYear){ {{ educationItem.completionYear }} год } +

    + +
    +
    +

    + {{ educationItem.description }} +

    + +

    + {{ educationItem.educationLevel }} +

    + +

    + {{ educationItem.educationStatus }} +

    +
    + +
    +
    + +
    + +
    + +
    +
    +
    +
    + } } +
    + } @if (editingStep === 'experience') { +
    +
    + @if (showWorkFields){ +
    +
    + @if (profileForm.get("entryYearWork"); as entryYearWork) { +
    + + + + + + @if (entryYearWork | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } @if (profileForm.get("completionYearWork"); as completionYearWork) { +
    + + + + + + @if (completionYearWork | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } +
    +
    -
    - @if(workItems().length || workExperience.length){ @for (workItem of workExperience.value; - track $index) { -
    -

    - {{ workItem.organizationName }} -

    +
    + @if (profileForm.get("organization"); as organization) { +
    + + + @if (organization | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } +
    -

    - @if(workItem.entryYear && workItem.completionYear) { - {{ workItem.entryYear }} год • {{ workItem.completionYear }} год } @else if - (workItem.entryYear && !workItem.completionYear) { {{ workItem.entryYear }} год } - @else if (!workItem.entryYear && workItem.completionYear){ - {{ workItem.completionYear }} год } -

    +
    + @if (profileForm.get("jobPosition"); as jobPosition) { +
    + + + @if (jobPosition | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } +
    -
    -
    -

    - {{ workItem.description }} -

    +
    + @if (profileForm.get("descriptionWork"); as descriptionWork) { +
    + + + @if (descriptionWork | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } +
    + } -

    - {{ workItem.jobPosition }} -

    -
    + + добавить работу + + +
    -
    -
    - +
    + @if(workItems().length || workExperience.length){ @for (workItem of + workExperience.value; track $index) { +
    +

    + {{ workItem.organizationName }} +

    + +

    + @if(workItem.entryYear && workItem.completionYear) { + {{ workItem.entryYear }} год • {{ workItem.completionYear }} год } @else if + (workItem.entryYear && !workItem.completionYear) { {{ workItem.entryYear }} год } + @else if (!workItem.entryYear && workItem.completionYear){ + {{ workItem.completionYear }} год } +

    + +
    +
    +

    + {{ workItem.description }} +

    + +

    + {{ workItem.jobPosition }} +

    -
    - +
    +
    + +
    + +
    + +
    + } }
    - } }
    -
    - } @if(editingStep === 'achievements'){ - - } @if (editingStep === 'skills') { -
    -
    -
    - -
    - -
    -
    - + } @if (editingStep === 'skills') { +
    - @if (profileForm.get("skills"); as skills) { -
    - -
    - } -
    -
    - -
    -
    - @if (profileForm.get("language"); as language) {
    - - - - - - @if (language | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} + +
    +
    - }
    - } @if (profileForm.get("languageLevel"); as languageLevel) { -
    - - - - - @if (languageLevel | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    +
    + @if (profileForm.get("skills"); as skills) { +
    + +
    } -
    - } +
    - количество добавляемых языков не более 4-х +
    +
    + @if (profileForm.get("language"); as language) { +
    + + + + - + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } @if (profileForm.get("languageLevel"); as languageLevel) { +
    + + + + + + @if (languageLevel | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } +

    + + количество добавляемых языков не более 4-х + + - добавить язык - - - -
    - @if(languageItems().length || userLanguages.length){ @for (languageItem of - userLanguages.value; track $index) { -
    -
    -

    - {{ languageItem.language }} -

    - -
    -
    - -
    + (click)="addLanguage()" + > + добавить язык + + -
    - +
    + @if(languageItems().length || userLanguages.length){ @for (languageItem of + userLanguages.value; track $index) { +
    +
    +

    + {{ languageItem.language }} +

    + +
    +
    + +
    + +
    + +
    -
    -

    {{ languageItem.languageLevel }}

    +

    {{ languageItem.languageLevel }}

    +
    + } }
    - } }
    + } @else if (editingStep === 'settings') { +
    + удалить профиль +
    + }
    - } @else if (editingStep === 'settings') { -
    - удалить профиль + + + } + + +
    +
    + +

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

    + @if (isModalErrorSkillChooseText()) { +

    {{ isModalErrorSkillChooseText() }}.

    + } @else { +

    + для публикации профиля, нужно заполнить все обязательные поля (они будут + подсвечены красным). +

    }
    - - -} - - -
    -
    - -

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

    -
    - @if (isModalErrorSkillChooseText()) { -

    {{ isModalErrorSkillChooseText() }}.

    - } @else { -

    - для публикации профиля, нужно заполнить все обязательные поля (они будут - подсвечены красным). -

    - } -
    -
    - - -
    -
    -

    подтвердите удаление аккаунта

    - -
    + + + +
    +
    +

    подтвердите удаление аккаунта

    + +
    - удалить аккаунт -
    -
    - - -
    } - -

    +
    + +
    +
    + @if (profileForm.get("firstName"); as firstName) { +
    + + + @if (firstName | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } @if (profileForm.get("lastName"); as lastName) { +
    + + + @if (lastName | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } +
    + +
    + @if (profileForm.get("city"); as city) { +
    + + + @if (city | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } @if (profileForm.get("birthday"); as birthday) { +
    + + + @if (birthday | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } +
    -
    - @if(editingStep === 'main'){ -
    -
    - @if (profileForm.get("avatar"); as avatar) { -
    - - - @if (avatar | controlError: "required") { +
    + @if (profileForm.get("userType"); as userType) { @if (userType.value !== 1) { +
    + + @if (roles | async; as options) { + + } @if (userType | controlError: "required") {
    - {{ errorMessage.EMPTY_AVATAR }} + {{ errorMessage.VALIDATION_REQUIRED }}
    }
    - } @if (profileForm.get("coverImageAddress"); as coverImageAddress) { -
    - + +
    + +
    + @if (speciality | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } +
    +
    + +
    + @if (profileForm.get("aboutMe"); as aboutMe) { +
    + + + @if (aboutMe | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } +
    +
    + } @if (editingStep === 'education') { +
    +
    + @if (showEducationFields) { +
    + @if (profileForm.get("entryYear"); as entryYear) { +
    + + - + + + @if (entryYear | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } @if (profileForm.get("completionYear"); as completionYear) { +
    + + - - -

    - обложка формата -
    - .JPG или .JPEG весом до 50МБ -

    - @if (coverImageAddress | controlError: "required") { -

    загрузите файл

    - } -
    - + +
    + @if (completionYear | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + }
    }
    -
    -
    - @if (profileForm.get("firstName"); as firstName) { -
    - - - @if (firstName | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    - } @if (profileForm.get("lastName"); as lastName) { -
    - - - @if (lastName | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    +
    + @if (profileForm.get("organizationName"); as organizationName) { +
    + + + @if (organizationName | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    } -
    + + } +
    -
    - @if (profileForm.get("city"); as city) { -
    - - - @if (city | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    - } @if (profileForm.get("birthday"); as birthday) { -
    - - - @if (birthday | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    +
    + @if (profileForm.get("description"); as description) { +
    + + + @if (description | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    } -
    + + } +
    -
    - @if (profileForm.get("userType"); as userType) { @if (userType.value !== 1) { -
    - - @if (roles | async; as options) { - - } @if (userType | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    - } } @if (profileForm.get("speciality"); as speciality) { -
    - -
    - -
    - @if (speciality | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    +
    + @if (profileForm.get("educationLevel"); as educationLevel) { +
    + + + + + @if (educationLevel | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    } -
    + + }
    - @if (profileForm.get("aboutMe"); as aboutMe) { + @if (profileForm.get("educationStatus"); as educationStatus) {
    - - статус + - @if (aboutMe | controlError: "required") { + [selectedId]="selectedEducationStatusId()" + formControlName="educationStatus" + placeholder="Студент" + [options]="educationStatusList" + > + + + @if (educationStatus | controlError: "required") {
    {{ errorMessage.VALIDATION_REQUIRED }}
    @@ -270,82 +396,109 @@

    редактирование профиля

    }
    + } + + + добавить образование + +
    - } @if (editingStep === 'education') { -
    + +
    + @if(educationItems().length || education.length){ @for (educationItem of education.value; + track $index) { +
    +

    + {{ educationItem.organizationName }} +

    + +

    + @if(educationItem.entryYear && educationItem.completionYear) { + {{ educationItem.entryYear }} год • {{ educationItem.completionYear }} год } @else if + (educationItem.entryYear && !educationItem.completionYear) { + {{ educationItem.entryYear }} год } @else if (!educationItem.entryYear && + educationItem.completionYear){ {{ educationItem.completionYear }} год } +

    + +
    +
    +

    + {{ educationItem.description }} +

    + +

    + {{ educationItem.educationLevel }} +

    + +

    + {{ educationItem.educationStatus }} +

    +
    + +
    +
    + +
    + +
    + +
    +
    +
    +
    + } } +
    +
    + } @if (editingStep === 'experience') { +
    +
    + @if (showWorkFields){
    - @if (showEducationFields) {
    - @if (profileForm.get("entryYear"); as entryYear) { + @if (profileForm.get("entryYearWork"); as entryYearWork) {
    - + - + - @if (entryYear | controlError: "required") { + @if (entryYearWork | controlError: "required") {
    {{ errorMessage.VALIDATION_REQUIRED }}
    }
    - } @if (profileForm.get("completionYear"); as completionYear) { + } @if (profileForm.get("completionYearWork"); as completionYearWork) {
    - + - + - @if (completionYear | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    - } -
    -
    - @if (profileForm.get("organizationName"); as organizationName) { -
    - - - @if (organizationName | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    - } -
    - -
    - @if (profileForm.get("description"); as description) { -
    - - - @if (description | controlError: "required") { + @if (completionYearWork | controlError: "required") {
    {{ errorMessage.VALIDATION_REQUIRED }}
    @@ -353,292 +506,138 @@

    редактирование профиля

    }
    +
    -
    - @if (profileForm.get("educationLevel"); as educationLevel) { -
    - - - - - @if (educationLevel | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    +
    + @if (profileForm.get("organization"); as organization) { +
    + + + @if (organization | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    } -
    + + } +
    -
    - @if (profileForm.get("educationStatus"); as educationStatus) { -
    - - - - - @if (educationStatus | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    +
    + @if (profileForm.get("jobPosition"); as jobPosition) { +
    + + + @if (jobPosition | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    } -
    + } - - - добавить образование - -
    -
    - @if(educationItems().length || education.length){ @for (educationItem of - education.value; track $index) { -
    -

    - {{ educationItem.organizationName }} -

    - -

    - @if(educationItem.entryYear && educationItem.completionYear) { - {{ educationItem.entryYear }} год • {{ educationItem.completionYear }} год } @else - if (educationItem.entryYear && !educationItem.completionYear) { - {{ educationItem.entryYear }} год } @else if (!educationItem.entryYear && - educationItem.completionYear){ {{ educationItem.completionYear }} год } -

    - -
    -
    -

    - {{ educationItem.description }} -

    - -

    - {{ educationItem.educationLevel }} -

    - -

    - {{ educationItem.educationStatus }} -

    -
    - -
    -
    - -
    - -
    - -
    -
    +
    + @if (profileForm.get("descriptionWork"); as descriptionWork) { +
    + + + @if (descriptionWork | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }}
    -
    - } } + } + + }
    + } + + + добавить работу + +
    - } @if (editingStep === 'experience') { -
    -
    - @if (showWorkFields){ -
    -
    - @if (profileForm.get("entryYearWork"); as entryYearWork) { -
    - - - - - - @if (entryYearWork | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    - } @if (profileForm.get("completionYearWork"); as completionYearWork) { -
    - - - - - - @if (completionYearWork | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    - } -
    -
    -
    - @if (profileForm.get("organization"); as organization) { -
    - - - @if (organization | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    - } -
    +
    + @if(workItems().length || workExperience.length){ @for (workItem of workExperience.value; + track $index) { +
    +

    + {{ workItem.organizationName }} +

    -
    - @if (profileForm.get("jobPosition"); as jobPosition) { -
    - - - @if (jobPosition | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    - } -
    +

    + @if(workItem.entryYear && workItem.completionYear) { + {{ workItem.entryYear }} год • {{ workItem.completionYear }} год } @else if + (workItem.entryYear && !workItem.completionYear) { {{ workItem.entryYear }} год } + @else if (!workItem.entryYear && workItem.completionYear){ + {{ workItem.completionYear }} год } +

    -
    - @if (profileForm.get("descriptionWork"); as descriptionWork) { -
    - - - @if (descriptionWork | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    - } -
    - } +
    +
    +

    + {{ workItem.description }} +

    - - добавить работу - - -
    +

    + {{ workItem.jobPosition }} +

    +
    -
    - @if(workItems().length || workExperience.length){ @for (workItem of - workExperience.value; track $index) { -
    -

    - {{ workItem.organizationName }} -

    - -

    - @if(workItem.entryYear && workItem.completionYear) { - {{ workItem.entryYear }} год • {{ workItem.completionYear }} год } @else if - (workItem.entryYear && !workItem.completionYear) { {{ workItem.entryYear }} год } - @else if (!workItem.entryYear && workItem.completionYear){ - {{ workItem.completionYear }} год } -

    - -
    -
    -

    - {{ workItem.description }} -

    - -

    - {{ workItem.jobPosition }} -

    +
    +
    +
    -
    -
    - -
    - -
    - -
    +
    +
    - } }
    + } }
    - } @if(editingStep === 'achievements'){ - - } @if (editingStep === 'skills') { -
    + } @if (editingStep === 'skills') { +
    +
    +
    + +
    + +
    +
    +
    -
    - -
    - -
    + @if (profileForm.get("skills"); as skills) { +
    +
    - -
    - @if (profileForm.get("skills"); as skills) { -
    - -
    - } -
    + }
    +
    -
    -
    - @if (profileForm.get("language"); as language) { -
    - - - - +
    +
    + @if (profileForm.get("language"); as language) { +
    + + + + - @if (language | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    - } @if (profileForm.get("languageLevel"); as languageLevel) { -
    - - - - + @if (language | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } +
    + } @if (profileForm.get("languageLevel"); as languageLevel) { +
    + + + + - @if (languageLevel | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } -
    + @if (languageLevel | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    } -
    +
    + } +
    - количество добавляемых языков не более 4-х + количество добавляемых языков не более 4-х - - добавить язык - - + (click)="addLanguage()" + > + добавить язык + + -
    - @if(languageItems().length || userLanguages.length){ @for (languageItem of - userLanguages.value; track $index) { -
    -
    -

    - {{ languageItem.language }} -

    - -
    -
    - -
    - -
    - -
    +
    + @if(languageItems().length || userLanguages.length){ @for (languageItem of + userLanguages.value; track $index) { +
    +
    +

    + {{ languageItem.language }} +

    + +
    +
    +
    -
    -

    {{ languageItem.languageLevel }}

    +
    + +
    +
    - } } + +

    {{ languageItem.languageLevel }}

    + } }
    - } @else if (editingStep === 'settings') { -
    - удалить профиль -
    - }
    - - - } - - -
    -
    - -

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

    + } @else if (editingStep === 'settings') { +
    + удалить профиль
    - @if (isModalErrorSkillChooseText()) { -

    {{ isModalErrorSkillChooseText() }}.

    - } @else { -

    - для публикации профиля, нужно заполнить все обязательные поля (они будут - подсвечены красным). -

    }
    - - - -
    -
    -

    подтвердите удаление аккаунта

    - -
    - - удалить аккаунт + + +} + + +
    +
    + +

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

    - - - - - } @if (user.programs.length; as programsLength) { -
    -
      - @for (p of user.programs.slice(0, 3); track p.id) { -
    • - -
    • - } -
    -
    - @if (user.programs) { -
      - @for (program of user.programs.slice(3); track program.id) { -
    • - -
    • - } -
    - } -
    - -
    -
    - - program logo - -
    -
    -
    - @if (programsLength > 3) { -
    - {{ readAllPrograms ? "Скрыть" : "Читать полностью" }} -
    - } -
    }
    - - - - } diff --git a/projects/social_platform/src/app/office/profile/detail/main/main.component.scss b/projects/social_platform/src/app/office/profile/detail/main/main.component.scss index 837109a7c..c93650b63 100644 --- a/projects/social_platform/src/app/office/profile/detail/main/main.component.scss +++ b/projects/social_platform/src/app/office/profile/detail/main/main.component.scss @@ -232,13 +232,6 @@ } } -.approves { - &__text { - color: var(--black); - white-space: nowrap; - } -} - .about { padding: 24px; background-color: var(--light-white); @@ -256,14 +249,6 @@ } } - &__skills { - margin: 8px 0; - } - - &__skill { - display: inline-flex; - } - &__title { margin-bottom: 8px; color: var(--accent); diff --git a/projects/social_platform/src/app/office/profile/detail/main/main.component.ts b/projects/social_platform/src/app/office/profile/detail/main/main.component.ts index d4da3a312..d0a08aa0c 100644 --- a/projects/social_platform/src/app/office/profile/detail/main/main.component.ts +++ b/projects/social_platform/src/app/office/profile/detail/main/main.component.ts @@ -70,31 +70,24 @@ import { DirectionItem, directionItemBuilder } from "@utils/helpers/directionIte standalone: true, imports: [ CommonModule, - TagComponent, IconComponent, ModalComponent, - AvatarComponent, RouterLink, NgTemplateOutlet, UserLinksPipe, ParseBreaksPipe, ParseLinksPipe, YearsFromBirthdayPipe, - PluralizePipe, - AvatarComponent, NewsCardComponent, NewsFormComponent, - SoonCardComponent, ProjectDirectionCard, ], changeDetection: ChangeDetectionStrategy.OnPush, }) export class ProfileMainComponent implements OnInit, AfterViewInit, OnDestroy { private readonly route = inject(ActivatedRoute); - private readonly authService = inject(AuthService); private readonly profileNewsService = inject(ProfileNewsService); private readonly profileDataService = inject(ProfileDataService); - private readonly profileApproveSkillService = inject(ProfileService); private readonly cdRef = inject(ChangeDetectorRef); user?: User; @@ -186,9 +179,7 @@ export class ProfileMainComponent implements OnInit, AfterViewInit, OnDestroy { readAllEducation = false; readAllLanguages = false; readAllWorkExperience = false; - readAllModal = false; - approveOwnSkillModal = false; isShowModal = false; @ViewChild(NewsFormComponent) newsFormComponent?: NewsFormComponent; @@ -273,88 +264,6 @@ export class ProfileMainComponent implements OnInit, AfterViewInit, OnDestroy { this.readFullDescription = !isExpanded; } - /** - * Подтверждение или отмена подтверждения навыка пользователя - * @param skillId - идентификатор навыка - * @param event - событие клика для предотвращения всплытия - * @param skill - объект навыка для обновления - */ - onToggleApprove(skillId: number, event: Event, skill: Skill, profileId: number) { - event.stopPropagation(); - const userId = this.route.snapshot.params["id"]; - - const isApprovedByCurrentUser = skill.approves.some(approve => { - return approve.confirmedBy.id === profileId; - }); - - if (isApprovedByCurrentUser) { - this.profileApproveSkillService.unApproveSkill(userId, skillId).subscribe(() => { - skill.approves = skill.approves.filter(approve => approve.confirmedBy.id !== profileId); - this.cdRef.markForCheck(); - }); - } else { - this.profileApproveSkillService - .approveSkill(userId, skillId) - .pipe( - switchMap(newApprove => - newApprove.confirmedBy - ? of(newApprove) - : this.authService.profile.pipe( - map(profile => ({ - ...newApprove, - confirmedBy: profile, - })) - ) - ) - ) - .subscribe({ - next: updatedApprove => { - skill.approves = [...skill.approves, updatedApprove]; - this.cdRef.markForCheck(); - }, - error: err => { - if (err instanceof HttpErrorResponse) { - if (err.status === 400) { - this.approveOwnSkillModal = true; - this.cdRef.markForCheck(); - } - } - }, - }); - } - } - - isUserApproveSkill(skill: Skill, profileId: number): boolean { - return skill.approves.some(approve => approve.confirmedBy.id === profileId); - } - - openSkills: any = {}; - - /** - * Открытие модального окна с информацией о подтверждениях навыка - * @param skillId - идентификатор навыка - */ - onOpenSkill(skillId: number) { - this.openSkills[skillId] = !this.openSkills[skillId]; - } - - /** - * Обработчик изменения состояния модального окна навыка - * @param event - новое состояние модального окна - * @param skillId - идентификатор навыка - */ - onOpenChange(event: boolean, skillId: number) { - if (this.openSkills[skillId] && !event) { - this.openSkills[skillId] = false; - } else { - this.openSkills[skillId] = event; - } - } - - onCloseModal(skillId: number) { - this.openSkills[skillId] = false; - } - openWorkInfoModal(): void { this.isShowModal = true; } diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.html b/projects/social_platform/src/app/office/profile/edit/edit.component.html index 43c27445a..ea78570fc 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.html +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.html @@ -269,6 +269,65 @@

    редактирование профиля

    }
    + +
    } @if (editingStep === 'education') {
    diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.scss b/projects/social_platform/src/app/office/profile/edit/edit.component.scss index 6a99652b0..203e9f712 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.scss +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.scss @@ -161,6 +161,7 @@ display: flex; gap: 20px; align-items: center; + width: 100%; } &__column { @@ -196,6 +197,12 @@ gap: 12px; } +.profile__wrapper--links { + display: grid; + grid-template-columns: 7fr 3fr; + grid-gap: 20px; +} + .profile__wrapper--education { display: grid; grid-template-columns: 6fr 4fr; diff --git a/projects/social_platform/src/app/office/projects/detail/chat/chat.component.html b/projects/social_platform/src/app/office/projects/detail/chat/chat.component.html index 9d7a9ab50..c73d6fe13 100644 --- a/projects/social_platform/src/app/office/projects/detail/chat/chat.component.html +++ b/projects/social_platform/src/app/office/projects/detail/chat/chat.component.html @@ -55,10 +55,10 @@

    файлы

    @for (file of chatFiles; track $index) {
  • } diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component.html b/projects/social_platform/src/app/office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component.html index ca37af56f..7f83d02d8 100644 --- a/projects/social_platform/src/app/office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component.html +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component.html @@ -2,16 +2,37 @@
    + @if (type === 'vacancies') { +
    + +
    +

    {{ vacancy.project.name }}

    +

    19.08.25 20:15

    +
    +
    + } @else {

    {{ vacancy.role }}

    -
    - -

    {{ vacancy.salary ?? "по договоренности" }}

    + } +
    + @if (type === 'vacancies') { +

    {{ vacancy.role }}

    + } +
    + +

    {{ vacancy.salary ?? "по договоренности" }}

    +
    - @for (skill of vacancy.requiredSkills.slice(0, 5); track $index) { + @for (skill of vacancy.requiredSkills.slice(0, endSliceOfSkills); track $index) { {{ skill.name }} -
    +
    подробнее - откликнуться
    diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component.scss b/projects/social_platform/src/app/office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component.scss index 6d046a121..e4a2bb69f 100644 --- a/projects/social_platform/src/app/office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component.scss +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component.scss @@ -9,6 +9,25 @@ justify-content: space-between; padding-bottom: 8px; border-bottom: 0.5px solid var(--accent); + + &--top { + display: flex; + flex-direction: column; + gap: 3px; + align-items: flex-end; + } + } + + &__project { + display: flex; + gap: 10px; + align-items: center; + + &--info { + p { + color: var(--black); + } + } } &__role { @@ -28,8 +47,19 @@ &__actions { display: flex; + gap: 20px; align-items: center; justify-content: space-between; + + &--vacancies { + ::ng-deep { + app-button { + button { + width: 131px; + } + } + } + } } &__skills { diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component.ts b/projects/social_platform/src/app/office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component.ts index 3441a2a8c..c7fca5b2b 100644 --- a/projects/social_platform/src/app/office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component.ts +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component.ts @@ -8,6 +8,7 @@ import { ButtonComponent } from "@ui/components"; import { ParseBreaksPipe, ParseLinksPipe } from "@corelib"; import { expandElement } from "@utils/expand-element"; import { TagComponent } from "@ui/components/tag/tag.component"; +import { AvatarComponent } from "@ui/components/avatar/avatar.component"; /** * КОМПОНЕНТ КАРТОЧКИ ВАКАНСИИ ПРОЕКТА @@ -38,17 +39,26 @@ import { TagComponent } from "@ui/components/tag/tag.component"; ParseLinksPipe, ParseBreaksPipe, TagComponent, + AvatarComponent, ], templateUrl: "./project-vacancy-card.component.html", styleUrl: "./project-vacancy-card.component.scss", }) export class ProjectVacancyCardComponent implements OnInit { @Input({ required: true }) vacancy!: Vacancy; // Данные вакансии (обязательное поле) + @Input() type: "vacancies" | "project" = "project"; - ngOnInit(): void {} + ngOnInit(): void { + if (this.type === "project") { + this.endSliceOfSkills = 5; + } else { + this.endSliceOfSkills = 3; + } + } descriptionExpandable!: boolean; // Флаг необходимости кнопки "Читать полностью" readFullDescription = false; // Флаг показа всех вакансий + endSliceOfSkills = 0; /** * Раскрытие/сворачивание описания профиля diff --git a/projects/social_platform/src/app/office/vacancies/detail/info/info.component.html b/projects/social_platform/src/app/office/vacancies/detail/info/info.component.html index 24583ad61..a183bb58c 100644 --- a/projects/social_platform/src/app/office/vacancies/detail/info/info.component.html +++ b/projects/social_platform/src/app/office/vacancies/detail/info/info.component.html @@ -2,63 +2,15 @@ @if (vacancy) {
    -
    - cover -
    -

    {{ vacancy.role }}

    -
    -
    -
    -
    - @if (project.region) { - - {{ project.region | capitalize }} - } @else { - Не указано - } -
    -
    - {{ vacancy.workFormat ? (vacancy.workFormat | capitalize) : "Формат работы не указан" }} -
    -
    - {{ vacancy.workSchedule ? (vacancy.workSchedule | capitalize) : "График не указан" }} -
    -
    - -
    -
    - {{ - vacancy.salary - ? (vacancy.salary | salaryTransform | capitalize) + " " + "рублей" - : "Зарплата не указана" - }} -
    -
    - {{ - vacancy.requiredExperience - ? (vacancy.requiredExperience.toLowerCase().includes("без опыта") - ? "" - : "Опыт" + " ") + (vacancy.requiredExperience | capitalize) - : "Опыт не указан" - }} -
    -
    -
    - @if (vacancy.description) {
    -

    Описание вакансии

    -
    +
    +

    описание вакансии

    + +
    +

    @if (descriptionExpandable) {
    {{ vacancy.role }}
    @if (vacancy.requiredSkills.length; as skillsLength) {
    -
    Необходимые навыки
    +
    +

    навыки

    + +
    @if (vacancy.requiredSkills; as requiredSkills) { @if (requiredSkills) {
      @for (skill of requiredSkills.slice(0, 8); track $index) { - {{ skill.name }} + {{ skill.name }} }
    }
    @if (requiredSkills) {
      - @for (skill of requiredSkills.slice(8); track $index) { + @for (skill of requiredSkills.slice(0, 8); track $index) { {{ skill.name }} }
    @@ -102,44 +61,190 @@

    {{ vacancy.role }}

    + @if (vacancy.project; as project) {
    - Откликнуться - Прокачать себя +
    + +

    {{ project.name }}

    + + откликнуться +
    + +
    +
    +

    метаданные

    + +
    + +
      +
    • + +

      + {{ project.region ? (project.region | capitalize) : "не указан" }} +

      +
    • + +
    • + +

      + {{ + vacancy.workFormat ? (vacancy.workFormat | capitalize) : "формат работы не указан" + }} +

      +
    • + +
    • + +

      + {{ + vacancy.requiredExperience + ? (vacancy.requiredExperience.toLowerCase().includes("без опыта") + ? "" + : "опыт" + " ") + (vacancy.requiredExperience | capitalize) + : "опыт не указан" + }} +

      +
    • + +
    • + +

      + {{ vacancy.workSchedule ? (vacancy.workSchedule | capitalize) : "график не указан" }} +

      +
    • -
      - -

      {{ vacancy.project.name }}

      +
    • + +

      + {{ + vacancy.salary + ? (vacancy.salary | salaryTransform | capitalize) + " " + "рублей" + : "по договоренности" + }} +

      +
    • +
    + + +
    + }
    -
    - - success -

    - Сделай первый шаг в карьере, в бизнесе, в жизни вместе с подпиской -

    +
    +

    отклик на вакансию

    +
    - Узнать подробнее + @if (sendForm.get("whyMe"); as whyMe) { +
    + + + @if (whyMe | controlError: "required") { +
    + {{ errorMessage.VALIDATION_REQUIRED }} +
    + } @if (whyMe | controlError: "maxlength") { +
    + {{ errorMessage.VALIDATION_TOO_LONG }} + @if (whyMe.errors) { + {{ whyMe.errors["maxlength"]["requiredLength"] }} + } +
    + } @if (whyMe | controlError: "minlength") { +
    + {{ errorMessage.VALIDATION_TOO_SHORT }} + @if (whyMe.errors) { + {{ whyMe.errors["minlength"]["requiredLength"] }} + } +
    + } +
    + } + + прикрепить резюме PROCOLLAB + +

    или

    + + @if (sendForm.get("accompanyingFile"); as accompanyingFile) { + +
    + + +
    + +

    + файл резюме в формате
    .pdf, .word весом до 50МБ +

    +
    + @if (accompanyingFile | controlError: "required") { +

    загрузите файл

    + } +
    +
    +
    + } + + отправить отклик + +
    + + + +
    + +

    отклик отправлен

    + перейти к вакансиям
    diff --git a/projects/social_platform/src/app/office/vacancies/detail/info/info.component.scss b/projects/social_platform/src/app/office/vacancies/detail/info/info.component.scss index acf96401a..e838f943a 100644 --- a/projects/social_platform/src/app/office/vacancies/detail/info/info.component.scss +++ b/projects/social_platform/src/app/office/vacancies/detail/info/info.component.scss @@ -29,77 +29,98 @@ } } -.vacancy { - padding-bottom: 100px; - - &__cover { - position: relative; - height: 230px; - margin-bottom: 20px; - border-radius: var(--rounded-xl); - - img { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - width: 100%; - height: 100%; - } +.lists { + &__section { + display: flex; + justify-content: space-between; + margin-bottom: 8px; + border-bottom: 0.5px solid var(--accent); } - &__info { + &__list { display: flex; - flex-wrap: wrap; - gap: 15px; - align-items: center; + flex-direction: column; + gap: 8px; } - &__region { - display: flex; - gap: 5px; - align-items: center; - justify-content: center; + &__icon { + color: var(--accent); } - .info__top, - .info__bottom { + &__title { + margin-bottom: 8px; + color: var(--accent); + } + + &__item { display: flex; - gap: 25px; + gap: 6px; align-items: center; - justify-content: center; - width: 100%; - } - &__info-element { - width: 100%; - padding: 12px; - text-align: center; - background-color: var(--white); - border: 1px solid var(--gray); - border-radius: var(--rounded-xl); + &--status { + padding: 8px; + border: 0.5px solid var(--medium-grey-for-outline); + border-radius: var(--rounded-xl); + } - @include typography.body-14; + &--title { + color: var(--black); + } + + i { + padding: 6px; + background-color: var(--light-white); + border: 0.5px solid var(--medium-grey-for-outline); + border-radius: var(--rounded-xl); + } + + p { + color: var(--accent); + } + + span { + cursor: pointer; + } } +} +.vacancy { &__content { - padding: 20px; - margin: 20px 0; - background-color: var(--white); - border: 1px solid var(--gray); - border-radius: var(--rounded-xl); + padding: 24px; + margin-bottom: 20px; + background-color: var(--light-white); + border: 0.5px solid var(--medium-grey-for-outline); + border-radius: var(--rounded-lg); } - &__title { - margin-bottom: 15px; - } + &__right { + display: flex; + flex-direction: column; + gap: 20px; + text-align: center; - .skills { - &__title { - margin-bottom: 15px; + &--title { + margin: 12px 0; + } + + &--project { + position: relative; + padding: 48px 24px 24px; + background-color: var(--light-white); + border: 0.5px solid var(--medium-grey-for-outline); + border-radius: var(--rounded-lg); + + &-image { + position: absolute; + top: -70px; + left: 50%; + display: block; + transform: translate(-50%, 50%); + } } + } + .skills { &__list { display: flex; flex-wrap: wrap; @@ -116,45 +137,9 @@ } &__split { - display: flex; - gap: 24px; - justify-content: space-between; - margin-top: 20px; - } - - &__right { - display: flex; - flex-basis: 20%; - flex-direction: column; - gap: 15px; - - ::ng-deep .button { - &.button--inline, - &.button--outline { - border-radius: var(--rounded-xl); - } - } - } - - &__left { - flex-basis: 80%; - } - - &__project { - display: flex; - gap: 15px; - align-items: center; - padding: 15px 12px; - cursor: pointer; - border: 1px solid; - border-color: var(--gradient); - border-radius: var(--rounded-xl); - } - - &__project-image { - width: 66px; - height: 66px; - border-radius: 100%; + display: grid; + grid-template-columns: 7fr 3fr; + gap: 20px; } .read-more { @@ -210,18 +195,50 @@ } } - &__about { - padding: 0 24px; + &__form { + display: flex; + flex-direction: column; + gap: 10px; + + label { + color: var(--black); + } + + &--or { + color: var(--grey-for-text); + text-align: center; + } + + &-error { + border: 0.5px solid var(--red); + } + + &--cv { + ::ng-deep { + app-upload-file { + .control { + height: 80px; + border-radius: var(--rounded-xl); + } + } + } + + &-empty { + display: flex; + flex-direction: column; + gap: 12px; + align-items: center; + color: var(--grey-for-text); + } + } } } .cancel { display: flex; flex-direction: column; - align-items: center; - justify-content: center; + width: 600px; max-height: calc(100vh - 40px); - padding: 20px; overflow-y: auto; &__top { @@ -229,7 +246,6 @@ flex-direction: column; align-items: center; justify-content: center; - width: 68%; } &__image { @@ -269,3 +285,34 @@ text-align: center; } } + +$succeed-modal-width: 310px; + +.succeed { + display: flex; + flex-direction: column; + align-items: center; + width: $succeed-modal-width; + + &__check { + margin-bottom: 18px; + color: var(--green); + } + + &__text { + margin-bottom: 18px; + color: var(--black); + } + + &__link { + width: $succeed-modal-width; + } +} + +.error { + color: var(--red); + + i { + color: var(--red); + } +} diff --git a/projects/social_platform/src/app/office/vacancies/detail/info/info.component.ts b/projects/social_platform/src/app/office/vacancies/detail/info/info.component.ts index 17f344e03..c93b5be99 100644 --- a/projects/social_platform/src/app/office/vacancies/detail/info/info.component.ts +++ b/projects/social_platform/src/app/office/vacancies/detail/info/info.component.ts @@ -12,10 +12,12 @@ import { import { ActivatedRoute, Router, RouterModule } from "@angular/router"; import { AuthService } from "@auth/services"; import { + ControlErrorPipe, ParseBreaksPipe, ParseLinksPipe, SubscriptionPlan, SubscriptionPlansService, + ValidationService, } from "@corelib"; import { Project } from "@office/models/project.model"; import { Vacancy } from "@office/models/vacancy.model"; @@ -28,6 +30,13 @@ import { expandElement } from "@utils/expand-element"; import { SalaryTransformPipe } from "projects/core/src/lib/pipes/salary-transform.pipe"; import { map, Subscription } from "rxjs"; import { CapitalizePipe } from "projects/core/src/lib/pipes/capitalize.pipe"; +import { AvatarComponent } from "@ui/components/avatar/avatar.component"; +import { UserLinksPipe } from "@core/pipes/user-links.pipe"; +import { UploadFileComponent } from "@ui/components/upload-file/upload-file.component"; +import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms"; +import { ErrorMessage } from "@error/models/error-message"; +import { VacancyService } from "@office/services/vacancy.service"; +import { TextareaComponent } from "@ui/components/textarea/textarea.component"; /** * Компонент отображения детальной информации о вакансии @@ -72,47 +81,67 @@ import { CapitalizePipe } from "projects/core/src/lib/pipes/capitalize.pipe"; ButtonComponent, ModalComponent, RouterModule, + ReactiveFormsModule, ParseBreaksPipe, ParseLinksPipe, SalaryTransformPipe, CapitalizePipe, + UserLinksPipe, + ControlErrorPipe, + AvatarComponent, + UploadFileComponent, + TextareaComponent, ], templateUrl: "./info.component.html", styleUrl: "./info.component.scss", }) export class VacancyInfoComponent implements OnInit { - route = inject(ActivatedRoute); - router = inject(Router); - projectService = inject(ProjectService); - authService = inject(AuthService); - subscriptionPlansService = inject(SubscriptionPlansService); - cdRef = inject(ChangeDetectorRef); + private readonly route = inject(ActivatedRoute); + private readonly vacancyService = inject(VacancyService); + private readonly validationService = inject(ValidationService); + private readonly cdRef = inject(ChangeDetectorRef); + private readonly fb = inject(FormBuilder); + + constructor() { + // Создание формы отклика с валидацией + this.sendForm = this.fb.group({ + // Мотивационное письмо: обязательное поле, минимум 20 символов, максимум 2000 + whyMe: ["", [Validators.required, Validators.minLength(20), Validators.maxLength(2000)]], + // Прикрепленный файл резюме: обязательное поле + accompanyingFile: ["", Validators.required], + }); + } vacancy!: Vacancy; - project!: Project; + + /** Объект с сообщениями об ошибках */ + errorMessage = ErrorMessage; + + descriptionExpandable!: boolean; + skillsExpandable!: boolean; + + /** Форма отправки отклика */ + sendForm: FormGroup; + + /** Флаг состояния отправки формы */ + sendFormIsSubmitting = false; + + /** Флаг отображения модального окна с результатом */ + resultModal = false; + + openModal = signal(false); + readFullDescription = false; + readFullSkills = false; + + private subscriptions$: Subscription[] = []; + + @ViewChild("skillsEl") skillsEl?: ElementRef; + @ViewChild("descEl") descEl?: ElementRef; ngOnInit(): void { this.route.data.pipe(map(r => r["data"])).subscribe((vacancy: Vacancy) => { this.vacancy = vacancy; - - this.projectService.getOne(vacancy.project.id).subscribe((project: Project) => { - this.project = project; - }); }); - - const subscriptionsSub$ = this.subscriptionPlansService - .getSubscriptions() - .pipe( - map(subscription => { - if (Array.isArray(subscription)) { - return subscription; - } else return [subscription]; - }) - ) - .subscribe(subscriptions => { - this.subscriptions.set(subscriptions); - }); - this.subscriptions$.push(subscriptionsSub$); } ngAfterViewInit(): void { @@ -129,18 +158,35 @@ export class VacancyInfoComponent implements OnInit { this.subscriptions$.forEach($ => $.unsubscribe()); } - @ViewChild("skillsEl") skillsEl?: ElementRef; - @ViewChild("descEl") descEl?: ElementRef; - - subscriptions$: Subscription[] = []; - - openModal = signal(false); - - descriptionExpandable!: boolean; - skillsExpandable!: boolean; - - readFullDescription = false; - readFullSkills = false; + /** + * Обработчик отправки формы + * Валидирует форму и отправляет отклик на сервер + */ + onSubmit(): void { + // Проверка валидности формы + if (!this.validationService.getFormValidation(this.sendForm)) { + return; + } + + // Установка флага загрузки + this.sendFormIsSubmitting = true; + + // Отправка отклика на сервер + this.vacancyService + .sendResponse(Number(this.route.snapshot.paramMap.get("vacancyId")), this.sendForm.value) + .subscribe({ + next: () => { + // Успешная отправка - показываем модальное окно + this.sendFormIsSubmitting = false; + this.resultModal = true; + this.openModal.set(false); + }, + error: () => { + // Ошибка отправки - снимаем флаг загрузки + this.sendFormIsSubmitting = false; + }, + }); + } onExpandDescription(elem: HTMLElement, expandedClass: string, isExpanded: boolean): void { expandElement(elem, expandedClass, isExpanded); @@ -152,9 +198,6 @@ export class VacancyInfoComponent implements OnInit { this.readFullSkills = !isExpanded; } - openSubscription = signal(false); - subscriptions = signal([]); - openSkills() { location.href = "https://skills.procollab.ru"; } diff --git a/projects/social_platform/src/app/office/vacancies/detail/vacancies-detail.component.html b/projects/social_platform/src/app/office/vacancies/detail/vacancies-detail.component.html index 8cbd22285..d32471d20 100644 --- a/projects/social_platform/src/app/office/vacancies/detail/vacancies-detail.component.html +++ b/projects/social_platform/src/app/office/vacancies/detail/vacancies-detail.component.html @@ -2,7 +2,7 @@ @if (vacancy) {
    - +
    diff --git a/projects/social_platform/src/app/office/vacancies/detail/vacancies-detail.component.ts b/projects/social_platform/src/app/office/vacancies/detail/vacancies-detail.component.ts index 0c8a6f8d3..3621c2dce 100644 --- a/projects/social_platform/src/app/office/vacancies/detail/vacancies-detail.component.ts +++ b/projects/social_platform/src/app/office/vacancies/detail/vacancies-detail.component.ts @@ -6,6 +6,7 @@ import { ActivatedRoute, RouterOutlet } from "@angular/router"; import { Vacancy } from "@office/models/vacancy.model"; import { BarComponent } from "@ui/components"; import { map, Subscription } from "rxjs"; +import { BackComponent } from "@uilib"; /** * Компонент детального просмотра вакансии @@ -29,7 +30,7 @@ import { map, Subscription } from "rxjs"; @Component({ selector: "app-vacancies-detail", standalone: true, - imports: [CommonModule, BarComponent, RouterOutlet], + imports: [CommonModule, BarComponent, RouterOutlet, BackComponent], templateUrl: "./vacancies-detail.component.html", styleUrl: "./vacancies-detail.component.scss", }) diff --git a/projects/social_platform/src/app/office/vacancies/list/list.component.html b/projects/social_platform/src/app/office/vacancies/list/list.component.html index 056de427b..e4644b551 100644 --- a/projects/social_platform/src/app/office/vacancies/list/list.component.html +++ b/projects/social_platform/src/app/office/vacancies/list/list.component.html @@ -1,34 +1,28 @@ -
    +
    @if(type() === 'all'){ - +
    + @if(vacancyList().length){ @for (vacancy of vacancyList(); track $index) { + + + } } +
    + } @if (type() === 'my') { @if (responsesList().length) { @for (response of responsesList(); track + $index) { + } -
    -
    - @if(type() === 'all'){ - + + } @else { } }
    diff --git a/projects/social_platform/src/app/office/vacancies/list/list.component.scss b/projects/social_platform/src/app/office/vacancies/list/list.component.scss index 68942daf2..0d466d507 100644 --- a/projects/social_platform/src/app/office/vacancies/list/list.component.scss +++ b/projects/social_platform/src/app/office/vacancies/list/list.component.scss @@ -2,32 +2,10 @@ @use "styles/typography"; .page { - display: flex; - flex-direction: column; - gap: 20px; - - @include responsive.apply-desktop { - flex-direction: row-reverse; - align-items: flex-start; - } - - &__filter { - @include responsive.apply-desktop { - position: sticky; - top: 0; - flex-grow: 0.3; - max-width: 400px; - } - } - - &__feed { - display: flex; - flex-direction: column; - flex-grow: 0.7; - grid-column: unset; - gap: 20px; - align-items: center; - width: 100%; + &__vacancies { + display: grid; + grid-template-columns: 333px 333px; + grid-gap: 20px; } &__item { @@ -38,26 +16,38 @@ &__title { margin-top: 40px; color: var(--black); - - @include typography.heading-4; - - @include responsive.apply-desktop { - @include typography.heading-3; - } } } -.filter { +.cancel { display: flex; flex-direction: column; - gap: 10px; - width: 100%; + width: 350px; + height: 175px; + max-height: calc(100vh - 40px); + padding: 24px; + overflow-y: auto; + + &__top { + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + padding-bottom: 8px; + margin-bottom: 8px; + } + + &__title { + color: var(--grey-for-text); + text-align: center; + } + + &__icon { + color: var(--accent); + } - &__search { - padding: 10px; + &__text { + margin-bottom: 8px; color: var(--black); - background-color: var(--white); - border: 1px solid var(--grey-button); - border-radius: var(--rounded-lg); } } diff --git a/projects/social_platform/src/app/office/vacancies/list/list.component.ts b/projects/social_platform/src/app/office/vacancies/list/list.component.ts index 6f8f18418..cce757e48 100644 --- a/projects/social_platform/src/app/office/vacancies/list/list.component.ts +++ b/projects/social_platform/src/app/office/vacancies/list/list.component.ts @@ -1,11 +1,13 @@ /** @format */ +// list.component.ts +/** @format */ + import { Component, inject, signal } from "@angular/core"; import { CommonModule } from "@angular/common"; import { concatMap, debounceTime, - distinctUntilChanged, fromEvent, map, noop, @@ -15,90 +17,63 @@ import { tap, throttleTime, } from "rxjs"; -import { OpenVacancyComponent } from "@office/feed/shared/open-vacancy/open-vacancy.component"; import { VacancyService } from "@office/services/vacancy.service"; import { Vacancy } from "@office/models/vacancy.model"; import { ApiPagination } from "@office/models/api-pagination.model"; -import { ActivatedRoute, Router } from "@angular/router"; -import { VacancyFilterComponent } from "../shared/filter/vacancy-filter.component"; +import { ActivatedRoute, Router, RouterLink } from "@angular/router"; import { VacancyResponse } from "@office/models/vacancy-response.model"; import { ResponseCardComponent } from "@office/features/response-card/response-card.component"; -import { FormBuilder, FormGroup, ReactiveFormsModule } from "@angular/forms"; -import { SearchComponent } from "@ui/components/search/search.component"; +import { ProjectVacancyCardComponent } from "@office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component"; +import { ButtonComponent, IconComponent } from "@ui/components"; +import { ModalComponent } from "@ui/components/modal/modal.component"; -/** - * Компонент списка вакансий - * Отображает список всех вакансий или откликов пользователя с возможностью фильтрации и поиска - * Поддерживает бесконечную прокрутку для загрузки дополнительных данных - */ @Component({ selector: "app-vacancies-list", standalone: true, imports: [ CommonModule, - OpenVacancyComponent, - VacancyFilterComponent, ResponseCardComponent, - SearchComponent, - ReactiveFormsModule, + ProjectVacancyCardComponent, + ButtonComponent, + IconComponent, + ModalComponent, + RouterLink, ], templateUrl: "./list.component.html", styleUrl: "./list.component.scss", }) export class VacanciesListComponent { - /** Сервис для работы с маршрутами */ route = inject(ActivatedRoute); - /** Сервис роутера для навигации */ router = inject(Router); - /** Сервис для работы с вакансиями */ vacancyService = inject(VacancyService); - /** - * Конструктор компонента - * @param fb - FormBuilder для создания формы поиска - */ - constructor(private readonly fb: FormBuilder) { - // Создание формы поиска - this.searchForm = this.fb.group({ - search: [""], // Поле для поискового запроса - }); - } + totalItemsCount = signal(0); + vacancyList = signal([]); + responsesList = signal([]); + vacancyPage = signal(1); + perFetchTake = signal(20); + type = signal<"all" | "my" | null>(null); + + requiredExperience = signal(undefined); + roleContains = signal(undefined); + workFormat = signal(undefined); + workSchedule = signal(undefined); + salary = signal(undefined); + + isMyModal = signal(false); + + subscriptions$ = signal([]); - /** - * Инициализация компонента - * Определяет тип страницы (все вакансии или мои отклики) - * Настраивает подписки на изменения формы поиска и параметров маршрута - */ ngOnInit() { - // Определение типа страницы из URL const urlSegment = this.router.url.split("/").slice(-1)[0]; const trimmedSegment = urlSegment.split("?")[0]; this.type.set(trimmedSegment as "all" | "my"); - // Подписка на изменения поля поиска с задержкой - this.searchForm - .get("search") - ?.valueChanges.pipe( - debounceTime(300), // Задержка 300мс перед выполнением поиска - distinctUntilChanged(), // Выполнять только при изменении значения - tap(value => { - // Обновление параметров маршрута - this.router.navigate([], { - relativeTo: this.route, - queryParams: { role_contains: value || null }, - queryParamsHandling: "merge", - }); - }) - ) - .subscribe(); - - // Получение данных из резолвера маршрута const routeData$ = this.type() === "all" ? this.route.data.pipe(map(r => r["data"])) : this.route.data.pipe(map(r => r["data"])); - // Подписка на данные маршрута const subscription = routeData$.subscribe( (vacancy: ApiPagination | ApiPagination) => { if (this.type() === "all") { @@ -110,50 +85,43 @@ export class VacanciesListComponent { } ); - // Подписка на изменения параметров запроса для фильтрации const queryParams$ = this.route.queryParams .pipe( - debounceTime(200), // Задержка + debounceTime(200), tap(params => { - // Извлечение параметров фильтрации из URL const requiredExperience = params["required_experience"] ? params["required_experience"] : undefined; - // Установка значения поиска - this.searchForm - .get("search") - ?.setValue(params["role_contains"] || "", { emitEvent: false }); - + const roleContains = params["role_contains"] || undefined; const workFormat = params["work_format"] ? params["work_format"] : undefined; const workSchedule = params["work_schedule"] ? params["work_schedule"] : undefined; - const salaryMax = params["salary_max"] ? params["salary_max"] : undefined; - const salaryMin = params["salary_min"] ? params["salary_min"] : undefined; + const salary = params["salary"] ? params["salary"] : undefined; - // Обновление сигналов фильтрации this.requiredExperience.set(requiredExperience); + this.roleContains.set(roleContains); this.workFormat.set(workFormat); this.workSchedule.set(workSchedule); - this.salaryMin.set(salaryMin); - this.salaryMax.set(salaryMax); + this.salary.set(salary); }), switchMap(() => this.onFetch(0, 20)) ) .subscribe((result: any) => { - this.vacancyList.set(result.results); + if (this.type() === "all") { + this.vacancyList.set(result.results); + } + this.totalItemsCount.set(result.count); + this.vacancyPage.set(1); }); this.subscriptions$().push(subscription, queryParams$); + + this.myModalSetup(); } - /** - * Инициализация после отрисовки представления - * Настраивает обработчик прокрутки для бесконечной загрузки - */ ngAfterViewInit() { const target = document.querySelector(".office__body"); if (target) { - // Подписка на события прокрутки с троттлингом const scrollEvents$ = fromEvent(target, "scroll") .pipe( concatMap(() => this.onScroll()), @@ -165,64 +133,24 @@ export class VacanciesListComponent { } } - /** - * Очистка ресурсов при уничтожении компонента - */ ngOnDestroy() { this.subscriptions$().forEach(($: any) => $.unsubscribe()); } - /** Форма поиска */ - searchForm: FormGroup; - /** Общее количество элементов */ - totalItemsCount = signal(0); - /** Список вакансий */ - vacancyList = signal([]); - /** Список откликов */ - responsesList = signal([]); - /** Текущая страница */ - vacancyPage = signal(1); - /** Количество элементов на страницу */ - perFetchTake = signal(20); - /** Тип страницы: все вакансии или мои отклики */ - type = signal<"all" | "my" | null>(null); - - // Сигналы для фильтрации - /** Фильтр по требуемому опыту */ - requiredExperience = signal(undefined); - /** Фильтр по формату работы */ - workFormat = signal(undefined); - /** Фильтр по графику работы */ - workSchedule = signal(undefined); - /** Минимальная зарплата */ - salaryMin = signal(undefined); - /** Максимальная зарплата */ - salaryMax = signal(undefined); - - /** Массив подписок для очистки */ - subscriptions$ = signal([]); - - /** - * Обработчик прокрутки для бесконечной загрузки - * @returns Observable с дополнительными данными или пустой объект - */ - onScroll() { - // Проверка, есть ли еще данные для загрузки + private onScroll() { if (this.totalItemsCount() && this.vacancyList().length >= this.totalItemsCount()) return of({}); const target = document.querySelector(".office__body"); if (!target) return of({}); - // Проверка позиции прокрутки const diff = target.scrollTop - target.scrollHeight + target.clientHeight; if (diff > 0) { - // Загрузка следующей порции данных return this.onFetch(this.vacancyPage() * this.perFetchTake(), this.perFetchTake()).pipe( - tap((vacancyChunk: Vacancy[]) => { + tap((result: any) => { this.vacancyPage.update(page => page + 1); - this.vacancyList.update(items => [...items, ...vacancyChunk]); + this.vacancyList.update(items => [...items, ...result.results]); }) ); } @@ -230,33 +158,7 @@ export class VacanciesListComponent { return of({}); } - /** - * Обработчик изменения значения поиска - * @param event - новое значение поиска - */ - onSearhValueChanged(event: string) { - this.searchForm.get("search")?.setValue(event); - } - - /** - * Обработчик отправки формы поиска - */ - onSearchSubmit() { - const value = this.searchForm.get("search")?.value; - this.router.navigate([], { - queryParams: { role_contains: value || null }, - queryParamsHandling: "merge", - relativeTo: this.route, - }); - } - - /** - * Загрузка данных с сервера - * @param offset - смещение для пагинации - * @param limit - количество элементов для загрузки - * @returns Observable с данными вакансий - */ - onFetch(offset: number, limit: number) { + private onFetch(offset: number, limit: number) { return this.vacancyService .getForProject( limit, @@ -265,16 +167,17 @@ export class VacanciesListComponent { this.requiredExperience(), this.workFormat(), this.workSchedule(), - this.salaryMin(), - this.salaryMax(), - this.searchForm.get("search")?.value + this.salary(), + this.roleContains() ) - .pipe( - tap((res: any) => { - this.totalItemsCount.set(res.count); - this.vacancyList.update(items => [...items, ...res.results]); - }), - map(res => res) - ); + .pipe(map(res => res)); + } + + private myModalSetup() { + if (this.type() === "my" && this.responsesList().length === 0) { + this.isMyModal.set(true); + } else { + this.isMyModal.set(false); + } } } diff --git a/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.html b/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.html index c8a704aa5..c953cf986 100644 --- a/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.html +++ b/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.html @@ -2,9 +2,14 @@ @@ -15,7 +20,7 @@

    Фильтр

    [class.select__input--open]="filterOpen()" (click)="filterOpen.set(!filterOpen())" > - Фильтр + фильтры
    Фильтр @if (filterOpen()) { }
    @@ -36,11 +41,11 @@

    Фильтр

    - Опыт + опыт
      @for (option of filterExperienceOptions; track $index) {
    • @@ -50,30 +55,30 @@

      Фильтр

    -
    - Формат работы +
    + график
      - @for (option of filterWorkFormatOptions; track $index) { + @for (option of filterWorkScheduleOptions; track $index) {
    • - + {{ option.label }}
    • }
    -
    - График +
    + формат работы
      - @for (option of filterWorkScheduleOptions; track $index) { + @for (option of filterWorkFormatOptions; track $index) {
    • - + {{ option.label }}
    • } @@ -81,28 +86,11 @@

      Фильтр

    - Заработная плата -
    - - - -
    - - - -
    + заработная плата +
  • + + с зарплатой +
  • diff --git a/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.scss b/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.scss index 060ea9912..62dc01b7b 100644 --- a/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.scss +++ b/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.scss @@ -8,13 +8,10 @@ .desktop { display: none; flex-direction: column; - gap: 20px; + gap: 16px; align-items: center; width: 100%; - padding: 20px; - background-color: var(--white); - border: 1px solid var(--medium-grey-for-outline); - border-radius: var(--rounded-xl); + border: 0.5px solid var(--medium-grey-for-outline); @include responsive.apply-desktop { display: flex; @@ -25,6 +22,10 @@ align-items: center; justify-content: space-between; width: 100%; + + a { + color: var(--accent); + } } } @@ -48,7 +49,6 @@ align-items: center; width: 100%; height: 38px; - padding: 13px 80px 13px 20px; background-color: var(--white); border: 1px solid var(--gray); border-radius: var(--rounded-lg); @@ -63,8 +63,6 @@ cursor: pointer; border-color: var(--accent); } - - @include typography.body-14; } &__dropdown { @@ -108,7 +106,7 @@ display: flex; align-items: center; margin-bottom: 6px; - color: var(--black); + color: var(--grey-for-text); cursor: pointer; app-checkbox { diff --git a/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.ts b/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.ts index d02211cbe..b1cf954a8 100644 --- a/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.ts +++ b/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.ts @@ -12,7 +12,7 @@ import { Output, signal, } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; +import { ActivatedRoute, Router, RouterLink } from "@angular/router"; import { ButtonComponent, CheckboxComponent, IconComponent, InputComponent } from "@ui/components"; import { ClickOutsideModule } from "ng-click-outside"; import { FeedService } from "@office/feed/services/feed.service"; @@ -32,10 +32,10 @@ import { filterWorkSchedule } from "projects/core/src/consts/filter-work-schedul imports: [ CommonModule, CheckboxComponent, - ButtonComponent, ClickOutsideModule, IconComponent, - InputComponent, + ButtonComponent, + RouterLink, ], templateUrl: "./vacancy-filter.component.html", styleUrl: "./vacancy-filter.component.scss", @@ -100,19 +100,8 @@ export class VacancyFilterComponent implements OnInit { currentWorkFormat = signal(undefined); /** Текущий фильтр по графику работы */ currentWorkSchedule = signal(undefined); - /** Текущая минимальная зарплата */ - currentSalaryMin = signal(undefined); - /** Текущая максимальная зарплата */ - currentSalaryMax = signal(undefined); - - // Сигналы для значений полей зарплаты - /** Значение поля минимальной зарплаты */ - salaryMinValue = signal(""); - /** Значение поля максимальной зарплаты */ - salaryMaxValue = signal(""); - - // Subject для debounce изменений зарплаты - private salaryChanges$ = new Subject<{ min: string; max: string }>(); + /** Текущая зарплата */ + currentSalary = signal(undefined); /** Опции фильтра по опыту работы */ readonly filterExperienceOptions = filterExperience; @@ -127,54 +116,14 @@ export class VacancyFilterComponent implements OnInit { * Инициализация компонента */ ngOnInit() { - // Подписка на изменения зарплаты с debounce - this.salaryChanges$.pipe(debounceTime(300)).subscribe(({ min, max }) => { - this.router.navigate([], { - queryParams: { - role_contains: this.searchValue || null, - salary_min: min || null, - salary_max: max || null, - }, - queryParamsHandling: "merge", - relativeTo: this.route, - }); - }); - // Подписка на изменения параметров запроса this.queries$ = this.route.queryParams.subscribe(queries => { // Синхронизация текущих значений фильтров с URL this.currentExperience.set(queries["required_experience"]); this.currentWorkFormat.set(queries["work_format"]); this.currentWorkSchedule.set(queries["work_schedule"]); - this.currentSalaryMin.set(queries["salary_min"]); - this.currentSalaryMax.set(queries["salary_max"]); + this.currentSalary.set(queries["salary"]); this.searchValue = queries["role_contains"]; - - // Синхронизация полей зарплаты - this.salaryMinValue.set(queries["salary_min"] || ""); - this.salaryMaxValue.set(queries["salary_max"] || ""); - }); - } - - /** - * Обработчик изменения минимальной зарплаты - */ - onSalaryMinChange(value: string): void { - this.salaryMinValue.set(value); - this.salaryChanges$.next({ - min: value, - max: this.salaryMaxValue(), - }); - } - - /** - * Обработчик изменения максимальной зарплаты - */ - onSalaryMaxChange(value: string): void { - this.salaryMaxValue.set(value); - this.salaryChanges$.next({ - min: this.salaryMinValue(), - max: value, }); } @@ -248,12 +197,6 @@ export class VacancyFilterComponent implements OnInit { this.currentExperience.set(undefined); this.currentWorkFormat.set(undefined); this.currentWorkSchedule.set(undefined); - this.currentSalaryMax.set(undefined); - this.currentSalaryMin.set(undefined); - - // Сбрасываем значения полей - this.salaryMinValue.set(""); - this.salaryMaxValue.set(""); this.onSearchValueChanged(""); @@ -263,8 +206,6 @@ export class VacancyFilterComponent implements OnInit { required_experience: null, work_format: null, work_schedule: null, - salary_min: null, - salary_max: null, role_contains: null, }, relativeTo: this.route, @@ -305,8 +246,6 @@ export class VacancyFilterComponent implements OnInit { this.currentExperience(), this.currentWorkFormat(), this.currentWorkSchedule(), - this.currentSalaryMin(), - this.currentSalaryMax(), this.searchValue ) .pipe( @@ -319,6 +258,5 @@ export class VacancyFilterComponent implements OnInit { ngOnDestroy() { this.queries$?.unsubscribe(); - this.salaryChanges$.complete(); } } diff --git a/projects/social_platform/src/app/office/vacancies/vacancies.component.html b/projects/social_platform/src/app/office/vacancies/vacancies.component.html index ae96e6525..cce86b38a 100644 --- a/projects/social_platform/src/app/office/vacancies/vacancies.component.html +++ b/projects/social_platform/src/app/office/vacancies/vacancies.component.html @@ -1,20 +1,35 @@ - + + + +
    +
    + + +
    +
    + @if(isAll) { +
    + +
    + } + + +
    - + @if (isAll) { +
    + +
    + } +
    +
    +
    diff --git a/projects/social_platform/src/app/office/vacancies/vacancies.component.scss b/projects/social_platform/src/app/office/vacancies/vacancies.component.scss index e69de29bb..b9019b352 100644 --- a/projects/social_platform/src/app/office/vacancies/vacancies.component.scss +++ b/projects/social_platform/src/app/office/vacancies/vacancies.component.scss @@ -0,0 +1,32 @@ +.page { + &__info { + display: flex; + gap: 20px; + justify-content: space-between; + padding-bottom: 100px; + + form { + width: 100%; + } + } + + &__outlet { + display: flex; + flex-direction: column; + flex-grow: 1; + gap: 20px; + width: 100%; + margin-top: 10px; + } + + &__create { + display: flex; + flex-direction: column; + gap: 20px; + align-items: center; + } + + &__left { + width: 100%; + } +} diff --git a/projects/social_platform/src/app/office/vacancies/vacancies.component.ts b/projects/social_platform/src/app/office/vacancies/vacancies.component.ts index d34172381..81b2bd86c 100644 --- a/projects/social_platform/src/app/office/vacancies/vacancies.component.ts +++ b/projects/social_platform/src/app/office/vacancies/vacancies.component.ts @@ -1,30 +1,83 @@ /** @format */ -import { Component } from "@angular/core"; +// vacancies.component.ts +/** @format */ + +import { Component, inject, OnInit } from "@angular/core"; import { CommonModule } from "@angular/common"; import { BarComponent } from "@ui/components"; -import { RouterOutlet } from "@angular/router"; - -/** - * Основной компонент модуля вакансий - * - * Функциональность: - * - Отображает навигационную панель с двумя вкладками: "Вакансии" и "Мои отклики" - * - Содержит router-outlet для отображения дочерних компонентов - * - Служит контейнером для всех страниц модуля вакансий - * - * Используемые компоненты: - * - BarComponent - навигационная панель с кнопкой "Назад" и ссылками - * - RouterOutlet - для отображения дочерних маршрутов - * - * @selector app-vacancies - * @standalone true - автономный компонент - */ +import { ActivatedRoute, Router, RouterOutlet } from "@angular/router"; +import { BackComponent } from "@uilib"; +import { SearchComponent } from "@ui/components/search/search.component"; +import { VacancyFilterComponent } from "./shared/filter/vacancy-filter.component"; +import { FormBuilder, FormGroup, ReactiveFormsModule } from "@angular/forms"; +import { debounceTime, distinctUntilChanged, tap } from "rxjs"; + @Component({ selector: "app-vacancies", standalone: true, - imports: [CommonModule, BarComponent, RouterOutlet], + imports: [ + CommonModule, + BarComponent, + RouterOutlet, + BackComponent, + SearchComponent, + VacancyFilterComponent, + ReactiveFormsModule, + ], templateUrl: "./vacancies.component.html", styleUrl: "./vacancies.component.scss", }) -export class VacanciesComponent {} +export class VacanciesComponent implements OnInit { + route = inject(ActivatedRoute); + router = inject(Router); + fb = inject(FormBuilder); + + searchForm: FormGroup; + + basePath = "/office/"; + + get isAll(): boolean { + return this.router.url.includes("/vacancies/all"); + } + + get isMy(): boolean { + return this.router.url.includes("/vacancies/my"); + } + + constructor() { + this.searchForm = this.fb.group({ + search: [""], + }); + } + + ngOnInit() { + this.searchForm + .get("search") + ?.valueChanges.pipe( + debounceTime(300), + distinctUntilChanged(), + tap(value => { + this.router.navigate([], { + relativeTo: this.route, + queryParams: { role_contains: value || null }, + queryParamsHandling: "merge", + }); + }) + ) + .subscribe(); + } + + onSearchSubmit() { + const value = this.searchForm.get("search")?.value; + this.router.navigate([], { + queryParams: { role_contains: value || null }, + queryParamsHandling: "merge", + relativeTo: this.route, + }); + } + + onSearhValueChanged(event: string) { + this.searchForm.get("search")?.setValue(event); + } +} diff --git a/projects/social_platform/src/app/office/vacancies/vacancies.routes.ts b/projects/social_platform/src/app/office/vacancies/vacancies.routes.ts index daa763e54..912059fd2 100644 --- a/projects/social_platform/src/app/office/vacancies/vacancies.routes.ts +++ b/projects/social_platform/src/app/office/vacancies/vacancies.routes.ts @@ -27,7 +27,6 @@ export const VACANCIES_ROUTES: Routes = [ }, { path: "my", - // Ленивая загрузка маршрутов для откликов пользователя loadChildren: () => import("./list/list.routes").then(c => c.VACANCY_LIST_ROUTES), }, { @@ -41,7 +40,6 @@ export const VACANCIES_ROUTES: Routes = [ }, { path: ":vacancyId", - // Ленивая загрузка маршрутов для детальной информации о вакансии loadChildren: () => import("./detail/vacancies-detail.routes").then(c => c.VACANCIES_DETAIL_ROUTES), }, diff --git a/projects/social_platform/src/app/office/vacancy/send/send.component.html b/projects/social_platform/src/app/office/vacancy/send/send.component.html deleted file mode 100644 index b1d419ab7..000000000 --- a/projects/social_platform/src/app/office/vacancy/send/send.component.html +++ /dev/null @@ -1,140 +0,0 @@ - -
    - - @if (authService.profile | async; as profile) { -
    - -
    - -

    Ваш отклик на вакансию отправлен

    - - Вернуться в профиль - -
    -
    -

    Отправить отклик

    -
    -
    -
    - -
    -

    {{ profile.userType | userRole | async }}

    -

    - {{ profile.firstName }} {{ profile.lastName }} -

    -

    {{ profile.speciality }} • {{ profile.city }}

    - @if (profile.skills) { -
      - @if(profile.skills.length > 5){ @for (skill of profile.skills.slice(0, 5); track - skill.id) { -
    • - {{ skill.name }} -
    • - } - ... - } @else { @for (skill of profile.skills; track skill.id) { -
    • - {{ skill.name }} -
    • - } } -
    - } -
    -
    -
    - @if (sendForm.get("whyMe"); as whyMe) { -
    - - - @if (whyMe | controlError: "required") { -
    - {{ errorMessage.VALIDATION_REQUIRED }} -
    - } @if (whyMe | controlError: "maxlength") { -
    - {{ errorMessage.VALIDATION_TOO_LONG }} - @if (whyMe.errors) { - {{ whyMe.errors["maxlength"]["requiredLength"] }} - } -
    - } @if (whyMe | controlError: "minlength") { -
    - {{ errorMessage.VALIDATION_TOO_SHORT }} - @if (whyMe.errors) { - {{ whyMe.errors["minlength"]["requiredLength"] }} - } -
    - } -
    - } @if (sendForm.get("accompanyingFile"); as accompanyingFile) { -
    -
    - - Прикрепить резюме от PROCOLLAB -
    -
    - - -
    - -

    Прикрепить резюме

    -
    -

    Резюме формата .pdf, .docx

    - @if (accompanyingFile | controlError: "required") { -

    Загрузите файл

    - } -
    -
    -
    -
    - } -
    -
    - -
    -
    -

    Памятка перед отправкой отклика

    - -
      - @for (note of noteList; track $index) { -
    1. {{ note.text }}
    2. - } -
    -
    - - - Отправить отклик - - -
    -
    -
    - } -
    diff --git a/projects/social_platform/src/app/office/vacancy/send/send.component.scss b/projects/social_platform/src/app/office/vacancy/send/send.component.scss deleted file mode 100644 index bea2c4205..000000000 --- a/projects/social_platform/src/app/office/vacancy/send/send.component.scss +++ /dev/null @@ -1,200 +0,0 @@ -/** @format */ - -@use "styles/responsive"; - -.vacancy { - @include responsive.apply-desktop { - padding: 24px; - margin-top: 24px; - background: var(--white); - border-radius: var(--rounded-md); - } - - &__inner { - @include responsive.apply-desktop { - display: flex; - gap: 120px; - justify-content: space-between; - } - } - - &__sender { - margin-bottom: 20px; - } - - &__slides-title { - max-width: 320px; - margin-top: 12px; - color: var(--black); - text-align: center; - } - - &__slides-text { - max-width: 275px; - margin-top: 12px; - color: var(--dark-grey); - text-align: center; - } - - &__slides-error { - margin-top: 12px; - color: var(--red); - - // &--active { - // border: 1px solid red; - // } - } - - &__slides-open-file { - color: var(--accent); - transition: color 0.2s; - - &:hover { - color: var(--accent-dark); - } - } - - &__buttons { - display: flex; - flex-direction: column; - gap: 24px; - } - - &__button { - display: flex; - flex-direction: column; - gap: 10px; - margin-top: 24px; - } - - &__right, - &__left { - flex-basis: 50%; - } -} - -.note { - padding: 24px; - padding-bottom: 70px; - margin-top: 15px; - margin-bottom: 60px; - border: 1px solid var(--gray); - border-radius: var(--rounded-xl); - - &__list { - margin-top: 24px; - margin-left: 17px; - - :last-child { - margin-bottom: 0; - } - } - - &__item { - margin-bottom: 20px; - list-style-type: decimal; - } -} - -.profile { - display: flex; - flex-direction: column; - align-items: flex-start; - padding: 16px; - background-color: var(--white); - border-radius: var(--rounded-md); - - @include responsive.apply-desktop { - flex-direction: row; - background-color: transparent; - border-radius: 0; - } - - &__body { - margin-top: 18px; - - @include responsive.apply-desktop { - margin-top: 0; - margin-left: 18px; - } - } - - &__status { - margin-bottom: 6px; - color: var(--dark-grey); - } - - &__name { - margin-bottom: 2px; - color: var(--black); - } - - &__info { - margin-bottom: 16px; - color: var(--dark-grey); - } - - &__skills { - display: flex; - flex-wrap: wrap; - } - - &__skill { - margin-bottom: 10px; - - &:not(:last-child) { - margin-right: 10px; - } - } -} - -.form { - display: flex; - flex-direction: column; - gap: 24px; - padding: 16px; - background-color: var(--white); - border-radius: var(--rounded-md); - - fieldset { - max-width: 616px !important; - } - - &__submit { - display: block; - margin-top: 20px; - - &-icon { - transform: rotate(90deg); - } - } -} - -$succeed-modal-width: 310px; - -.succeed { - display: flex; - flex-direction: column; - align-items: center; - width: $succeed-modal-width; - - &__check { - width: 97px; - margin-bottom: 18px; - color: var(--green); - } - - &__text { - margin-bottom: 18px; - color: var(--black); - } - - &__link { - width: $succeed-modal-width; - } -} - -.vacancy-bar { - padding: 9px 0; - margin-bottom: 12px; -} diff --git a/projects/social_platform/src/app/office/vacancy/send/send.component.spec.ts b/projects/social_platform/src/app/office/vacancy/send/send.component.spec.ts deleted file mode 100644 index e94cacfee..000000000 --- a/projects/social_platform/src/app/office/vacancy/send/send.component.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** @format */ - -import { ComponentFixture, TestBed } from "@angular/core/testing"; - -import { VacancySendComponent } from "./send.component"; -import { HttpClientTestingModule } from "@angular/common/http/testing"; -import { of } from "rxjs"; -import { AuthService } from "@auth/services"; -import { ReactiveFormsModule } from "@angular/forms"; -import { RouterTestingModule } from "@angular/router/testing"; - -describe("VacancySendComponent", () => { - let component: VacancySendComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - const authSpy = { - profile: of({}), - roles: of([]), - }; - - await TestBed.configureTestingModule({ - imports: [ - HttpClientTestingModule, - ReactiveFormsModule, - RouterTestingModule, - VacancySendComponent, - ], - providers: [{ provide: AuthService, useValue: authSpy }], - }).compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(VacancySendComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it("should create", () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/projects/social_platform/src/app/office/vacancy/send/send.component.ts b/projects/social_platform/src/app/office/vacancy/send/send.component.ts deleted file mode 100644 index 4904b9ab7..000000000 --- a/projects/social_platform/src/app/office/vacancy/send/send.component.ts +++ /dev/null @@ -1,123 +0,0 @@ -/** @format */ - -import { Component, OnInit } from "@angular/core"; -import { AuthService } from "@auth/services"; -import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms"; -import { ErrorMessage } from "@error/models/error-message"; -import { VacancyService } from "@services/vacancy.service"; -import { ControlErrorPipe, ValidationService } from "projects/core"; -import { ActivatedRoute, RouterLink } from "@angular/router"; -import { NavService } from "@services/nav.service"; -import { UserRolePipe } from "@core/pipes/user-role.pipe"; -import { TextareaComponent } from "@ui/components/textarea/textarea.component"; -import { TagComponent } from "@ui/components/tag/tag.component"; -import { AvatarComponent } from "@ui/components/avatar/avatar.component"; -import { BarComponent, ButtonComponent, IconComponent } from "@ui/components"; -import { ModalComponent } from "@ui/components/modal/modal.component"; -import { AsyncPipe } from "@angular/common"; -import { UploadFileComponent } from "@ui/components/upload-file/upload-file.component"; -import { noteList } from "projects/core/src/consts/note-list"; -import { BackComponent } from "@uilib"; - -/** - * Компонент для отправки отклика на вакансию - * Позволяет пользователю отправить мотивационное письмо и резюме на вакансию - */ -@Component({ - selector: "app-send", - templateUrl: "./send.component.html", - styleUrl: "./send.component.scss", - standalone: true, - imports: [ - ModalComponent, - IconComponent, - RouterLink, - ButtonComponent, - AvatarComponent, - TagComponent, - ReactiveFormsModule, - TextareaComponent, - AsyncPipe, - ControlErrorPipe, - UserRolePipe, - BarComponent, - UploadFileComponent, - BackComponent, - ], -}) -export class VacancySendComponent implements OnInit { - /** - * Конструктор компонента - * @param authService - сервис аутентификации для получения данных текущего пользователя - * @param fb - FormBuilder для создания реактивных форм - * @param vacancyService - сервис для работы с вакансиями - * @param validationService - сервис валидации форм - * @param route - текущий маршрут для получения параметров - * @param navService - сервис навигации для установки заголовка страницы - */ - constructor( - public readonly authService: AuthService, - private readonly fb: FormBuilder, - private readonly vacancyService: VacancyService, - private readonly validationService: ValidationService, - private readonly route: ActivatedRoute, - private readonly navService: NavService - ) { - // Создание формы отклика с валидацией - this.sendForm = this.fb.group({ - // Мотивационное письмо: обязательное поле, минимум 20 символов, максимум 2000 - whyMe: ["", [Validators.required, Validators.minLength(20), Validators.maxLength(2000)]], - // Прикрепленный файл резюме: обязательное поле - accompanyingFile: ["", Validators.required], - }); - } - - /** - * Инициализация компонента - * Устанавливает заголовок страницы - */ - ngOnInit(): void { - this.navService.setNavTitle("Отклик на вакансию"); - } - - /** Объект с сообщениями об ошибках */ - errorMessage = ErrorMessage; - /** Форма отправки отклика */ - sendForm: FormGroup; - /** Флаг состояния отправки формы */ - sendFormIsSubmitting = false; - /** Флаг отображения модального окна с результатом */ - resultModal = false; - - /** - * Обработчик отправки формы - * Валидирует форму и отправляет отклик на сервер - */ - onSubmit(): void { - // Проверка валидности формы - if (!this.validationService.getFormValidation(this.sendForm)) { - return; - } - - // Установка флага загрузки - this.sendFormIsSubmitting = true; - - // Отправка отклика на сервер - this.vacancyService - .sendResponse(Number(this.route.snapshot.paramMap.get("vacancyId")), this.sendForm.value) - .subscribe({ - next: () => { - // Успешная отправка - показываем модальное окно - this.sendFormIsSubmitting = false; - this.resultModal = true; - }, - error: () => { - // Ошибка отправки - снимаем флаг загрузки - this.sendFormIsSubmitting = false; - }, - }); - } - - /** Список советов для подготовки отклика */ - readonly noteList = noteList; -} diff --git a/projects/social_platform/src/app/ui/components/button/button.component.ts b/projects/social_platform/src/app/ui/components/button/button.component.ts index 2e40eb57e..14b0d7264 100644 --- a/projects/social_platform/src/app/ui/components/button/button.component.ts +++ b/projects/social_platform/src/app/ui/components/button/button.component.ts @@ -17,7 +17,6 @@ import { CommonModule } from "@angular/common"; * - backgroundColor: кастомный цвет фона * - disabled: состояние блокировки кнопки * - customTypographyClass: кастомный CSS класс для типографики - * - haveHint: наличие подсказки * - tooltipText: текст подсказки * - tooltipPosition: позиция подсказки * - tooltipWidth: ширина подсказки diff --git a/projects/social_platform/src/app/ui/components/chat-message/chat-message.component.scss b/projects/social_platform/src/app/ui/components/chat-message/chat-message.component.scss index d5b919b33..5c52a2260 100644 --- a/projects/social_platform/src/app/ui/components/chat-message/chat-message.component.scss +++ b/projects/social_platform/src/app/ui/components/chat-message/chat-message.component.scss @@ -103,7 +103,7 @@ padding: 10px; color: var(--dark); cursor: pointer; - border-radius: 10px; + border-radius: var(--rounded-xl); transition: background-color 0.2s; &--red { @@ -129,7 +129,7 @@ height: 100%; content: ""; background-color: var(--accent); - border-radius: 10px; + border-radius: var(--rounded-xl); } &__name { diff --git a/projects/social_platform/src/assets/icons/svg/straight-face.svg b/projects/social_platform/src/assets/icons/svg/straight-face.svg new file mode 100644 index 000000000..57965aaa4 --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/straight-face.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg index 013d12458..655a8efe7 100644 --- a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg +++ b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From a3c6bb5dcbe90103001bef3b346b7300a389d8be Mon Sep 17 00:00:00 2001 From: Awakich Date: Tue, 21 Oct 2025 00:38:08 +0300 Subject: [PATCH 106/126] change design of feed & feed-filter logic, open-vacany, new-project component, add const files for feed-filter --- projects/core/src/consts/feed-filter-list.ts | 34 ++++ projects/core/src/consts/note-list.ts | 34 ---- .../core/src/consts/resource-options-list.ts | 24 +++ .../news-card/news-card.component.html | 16 +- .../news-card/news-card.component.scss | 11 +- .../src/app/office/feed/feed.component.html | 9 +- .../src/app/office/feed/feed.component.scss | 26 +-- .../src/app/office/feed/feed.component.ts | 11 +- .../feed/filter/feed-filter.component.html | 32 ++-- .../feed/filter/feed-filter.component.scss | 100 ++++++----- .../feed/filter/feed-filter.component.ts | 53 +++--- .../new-project/new-project.component.html | 80 ++++++--- .../new-project/new-project.component.scss | 66 ++++---- .../new-project/new-project.component.ts | 34 ++-- .../open-vacancy/open-vacancy.component.html | 145 ++++++++++------ .../open-vacancy/open-vacancy.component.scss | 158 ++++++++---------- .../open-vacancy/open-vacancy.component.ts | 36 ++-- .../src/app/office/office.component.ts | 70 +++++--- ...roject-partner-resources-step.component.ts | 26 +-- .../filter/vacancy-filter.component.scss | 1 - .../components/button/button.component.scss | 6 + .../snackbar/snackbar.component.scss | 2 +- .../src/app/utils/generate-year-list.ts | 48 ------ .../src/app/utils/inviteToProjectMapper.ts | 1 - .../src/assets/icons/svg/comment.svg | 3 + .../src/assets/icons/svg/people-bold.svg | 4 +- .../src/assets/icons/svg/procollab.svg | 3 + .../src/assets/icons/svg/share.svg | 4 +- .../assets/icons/symbol/svg/sprite.css.svg | 2 +- .../vacancies-card-background.svg | 52 ++++++ 30 files changed, 593 insertions(+), 498 deletions(-) create mode 100644 projects/core/src/consts/feed-filter-list.ts delete mode 100644 projects/core/src/consts/note-list.ts create mode 100644 projects/core/src/consts/resource-options-list.ts delete mode 100644 projects/social_platform/src/app/utils/generate-year-list.ts create mode 100644 projects/social_platform/src/assets/icons/svg/comment.svg create mode 100644 projects/social_platform/src/assets/icons/svg/procollab.svg create mode 100644 projects/social_platform/src/assets/images/office/feed/vacancy-card/vacancies-card-background.svg diff --git a/projects/core/src/consts/feed-filter-list.ts b/projects/core/src/consts/feed-filter-list.ts new file mode 100644 index 000000000..cfbed7340 --- /dev/null +++ b/projects/core/src/consts/feed-filter-list.ts @@ -0,0 +1,34 @@ +/** @format */ + +export const feedFilterlist = [ + { + id: 1, + name: "новости проектов", + value: "projects", + icon: "projects", + }, + { + id: 2, + name: "свежие вакансии", + value: "vacancy", + icon: "suitcase", + }, + { + id: 3, + name: "новости сообщества", + value: "news", + icon: "people-bold", + }, + { + id: 4, + name: "новости программ", + value: "projects/1", + icon: "procollab", + }, + { + id: 5, + name: "образование", + value: "education", + icon: "trajectories", + }, +]; diff --git a/projects/core/src/consts/note-list.ts b/projects/core/src/consts/note-list.ts deleted file mode 100644 index d1cdf33a4..000000000 --- a/projects/core/src/consts/note-list.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** @format */ - -export const noteList = [ - { - text: "Проверьте описание вакансии.", - }, - { - text: "Адаптируйте резюме.", - }, - { - text: "Напишите сопроводительное письмо.", - }, - { - text: "Проверьте грамматику и орфографию.", - }, - { - text: "Убедитесь в правильности контактной информации.", - }, - { - text: "Подготовьте дополнительные документы.", - }, - { - text: "Проверьте форматирование.", - }, - { - text: "Убедитесь в соблюдении сроков.", - }, - { - text: "Сохраните копию.", - }, - { - text: "Будьте готовы к интервью.", - }, -]; diff --git a/projects/core/src/consts/resource-options-list.ts b/projects/core/src/consts/resource-options-list.ts new file mode 100644 index 000000000..7b0e90206 --- /dev/null +++ b/projects/core/src/consts/resource-options-list.ts @@ -0,0 +1,24 @@ +/** @format */ + +export const resourceOptionsList = [ + { + id: 1, + value: "infrastructure", + label: "Инфраструктурный", + }, + { + id: 2, + value: "staff", + label: "Кадровый", + }, + { + id: 3, + value: "financial", + label: "Финансовый", + }, + { + id: 4, + value: "information", + label: "Информационный", + }, +]; diff --git a/projects/social_platform/src/app/office/features/news-card/news-card.component.html b/projects/social_platform/src/app/office/features/news-card/news-card.component.html index 94c1982e4..35ad994ec 100644 --- a/projects/social_platform/src/app/office/features/news-card/news-card.component.html +++ b/projects/social_platform/src/app/office/features/news-card/news-card.component.html @@ -7,9 +7,7 @@ [src]="feedItem.imageAddress || placeholderUrl" [alt]="feedItem.name" /> -
    -
    {{ feedItem.name }}
    -
    +

    {{ feedItem.name }}

    @if (isOwner) {
    @@ -92,16 +90,16 @@ [class.footer__like--active]="feedItem.isUserLiked" (click)="like.emit(feedItem.id)" > - - {{ feedItem.likesCount }} + + {{ feedItem.likesCount > 0 ? feedItem.likesCount : "" }}
    @@ -119,10 +117,10 @@
    diff --git a/projects/social_platform/src/app/office/features/news-card/news-card.component.scss b/projects/social_platform/src/app/office/features/news-card/news-card.component.scss index 4d20a602f..39c6ad81d 100644 --- a/projects/social_platform/src/app/office/features/news-card/news-card.component.scss +++ b/projects/social_platform/src/app/office/features/news-card/news-card.component.scss @@ -57,6 +57,7 @@ &__title { display: flex; + align-items: center; } &__top { @@ -76,6 +77,7 @@ /* stylelint-disable value-no-vendor-prefix */ &__text { white-space: break-spaces; + color: var(--grey-for-text); p { display: -webkit-box; @@ -160,20 +162,13 @@ &__left { display: flex; align-items: center; + gap: 10px; } &__item { display: flex; align-items: center; color: var(--dark-grey); - - &:not(:last-child) { - margin-right: 5px; - } - - i { - margin-right: 3px; - } } &__like { diff --git a/projects/social_platform/src/app/office/feed/feed.component.html b/projects/social_platform/src/app/office/feed/feed.component.html index e6cc06fcd..74d485713 100644 --- a/projects/social_platform/src/app/office/feed/feed.component.html +++ b/projects/social_platform/src/app/office/feed/feed.component.html @@ -3,8 +3,13 @@
    - @for (item of feedItems(); track $index) { @if (item.typeModel === "vacancy") { } @else if - (item.typeModel === "project") { + @for (item of feedItems(); track $index) { @if (item.typeModel === "vacancy") { + + } @else if (item.typeModel === "project") { { diff --git a/projects/social_platform/src/app/office/feed/filter/feed-filter.component.html b/projects/social_platform/src/app/office/feed/filter/feed-filter.component.html index 9294fef6c..4c9eb88be 100644 --- a/projects/social_platform/src/app/office/feed/filter/feed-filter.component.html +++ b/projects/social_platform/src/app/office/feed/filter/feed-filter.component.html @@ -1,10 +1,6 @@
    -
    -

    Фильтр

    - Сбросить фильтр -
    @@ -27,26 +23,26 @@

    Фильтр

    @if (filterOpen()) { }
    -Написать новость -
    - Виды новостей -
      - @for (option of filterOptions; track $index) { -
    • - - {{ option.label }} -
    • - } -
    + @for (filterItem of filterOptions; track $index) { +
    +
    + +
    +

    {{ filterItem.name }}

    +
    + }
    diff --git a/projects/social_platform/src/app/office/feed/filter/feed-filter.component.scss b/projects/social_platform/src/app/office/feed/filter/feed-filter.component.scss index f8b2ccc4d..fdce0eda3 100644 --- a/projects/social_platform/src/app/office/feed/filter/feed-filter.component.scss +++ b/projects/social_platform/src/app/office/feed/filter/feed-filter.component.scss @@ -1,46 +1,6 @@ @use "styles/typography"; @use "styles/responsive"; -:host { - width: 100%; -} - -.desktop { - display: none; - flex-direction: column; - gap: 20px; - align-items: center; - width: 100%; - padding: 20px; - margin-bottom: 20px; - background-color: var(--white); - border: 1px solid var(--medium-grey-for-outline); - border-radius: var(--rounded-xl); - - @include responsive.apply-desktop { - display: flex; - } - - &__header { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - } - - &__actions { - align-self: flex-start; - width: 50%; - - app-button { - &::ng-deep .button--inline { - min-height: 38px; - padding: 0; - } - } - } -} - .mobile { display: grid; grid-template-columns: 0.6fr 0.4fr; @@ -114,7 +74,65 @@ } .filter { - width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + + &__option { + cursor: pointer; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background-color: var(--light-white); + border-radius: var(--rounded-xl); + border: 0.5px solid var(--medium-grey-for-outline); + padding: 40px 24px 12px 24px; + height: 66px; + position: relative; + + &--disable { + opacity: 0.5; + cursor: not-allowed; + } + + &--active { + i { + color: var(--light-white) !important; + } + + .filter__option--icon { + background-color: var(--accent); + } + } + + &--icon { + display: flex; + align-items: center; + justify-content: center; + width: 50px; + height: 50px; + border-radius: 50%; + position: absolute; + bottom: 100%; + left: 50%; + transform: translate(-50%, 50%); + background-color: var(--light-white); + border: 0.5px solid var(--medium-grey-for-outline); + + i { + color: var(--accent); + } + } + + p { + text-align: center; + color: var(--grey-for-text); + } + } + + span { display: block; diff --git a/projects/social_platform/src/app/office/feed/filter/feed-filter.component.ts b/projects/social_platform/src/app/office/feed/filter/feed-filter.component.ts index 684a1a981..fe4c68f19 100644 --- a/projects/social_platform/src/app/office/feed/filter/feed-filter.component.ts +++ b/projects/social_platform/src/app/office/feed/filter/feed-filter.component.ts @@ -17,6 +17,7 @@ import { FeedService } from "@office/feed/services/feed.service"; import { User } from "@auth/models/user.model"; import { AuthService } from "@auth/services"; import { Subscription } from "rxjs"; +import { feedFilterlist } from "projects/core/src/consts/feed-filter-list"; /** * КОМПОНЕНТ ФИЛЬТРАЦИИ ЛЕНТЫ @@ -85,9 +86,9 @@ export class FeedFilterComponent implements OnInit, OnDestroy { // Читаем активные фильтры из URL const routeSubscription = this.route.queryParams.subscribe(queries => { if (queries["includes"]) { - this.includedFilters.set(queries["includes"].split(this.feedService.FILTER_SPLIT_SYMBOL)); + this.includedFilters.set(queries["includes"]); } else { - this.includedFilters.set([]); + this.includedFilters.set(""); } }); @@ -108,14 +109,10 @@ export class FeedFilterComponent implements OnInit, OnDestroy { * - label: отображаемое название на русском языке * - value: значение для API запроса */ - filterOptions = [ - { label: "Новости", value: "news" }, - { label: "Вакансии", value: "vacancy" }, - { label: "Новости проектов", value: "project" }, - ]; + filterOptions = feedFilterlist; // Массив активных фильтров - includedFilters = signal([]); + includedFilters = signal(""); /** * ОБНОВЛЕНИЕ URL С ТЕКУЩИМИ ФИЛЬТРАМИ @@ -124,10 +121,7 @@ export class FeedFilterComponent implements OnInit, OnDestroy { * Вызывается автоматически при любом изменении фильтров. */ private updateUrl(): void { - const includesParam = - this.includedFilters().length > 0 - ? this.includedFilters().join(this.feedService.FILTER_SPLIT_SYMBOL) - : null; + const includesParam = this.includedFilters().length > 0 ? this.includedFilters() : null; this.router .navigate([], { @@ -144,27 +138,42 @@ export class FeedFilterComponent implements OnInit, OnDestroy { * ПЕРЕКЛЮЧЕНИЕ ФИЛЬТРА С МГНОВЕННЫМ ОБНОВЛЕНИЕМ URL * * ЧТО ПРИНИМАЕТ: + * @param id - id для фильтра * @param keyword - значение фильтра для переключения * * ЧТО ДЕЛАЕТ: * - Добавляет фильтр, если он не активен * - Удаляет фильтр, если он уже активен + * - Обрабатывает переключение между projects и projects/1 * - Мгновенно обновляет URL параметры */ setFilter(keyword: string): void { this.includedFilters.update(included => { - const newIncluded = [...included]; + if (keyword.startsWith("projects/")) { + // Если уже активен этот же вложенный фильтр - сбрасываем к "projects" + if (included === keyword) { + return "projects"; + } + return keyword; + } - if (newIncluded.indexOf(keyword) !== -1) { - // Удаляем фильтр, если он уже активен - const idx = newIncluded.indexOf(keyword); - newIncluded.splice(idx, 1); - } else { - // Добавляем новый фильтр - newIncluded.push(keyword); + // Если кликнули на "projects" + if (keyword === "projects") { + if (included.startsWith("projects/")) { + return "projects"; + } + + if (included === "projects") { + return ""; + } + + return "projects"; } - return newIncluded; + if (included === keyword) { + return ""; + } + return keyword; }); // Мгновенно обновляем URL @@ -180,7 +189,7 @@ export class FeedFilterComponent implements OnInit, OnDestroy { * - Возвращает ленту к состоянию по умолчанию */ resetFilter(): void { - this.includedFilters.set([]); + this.includedFilters.set(""); this.updateUrl(); } diff --git a/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.html b/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.html index 3f0595d5a..53d887588 100644 --- a/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.html +++ b/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.html @@ -1,25 +1,63 @@
    -

    - Добро пожаловать на платформу! - {{ feedItem.name }} -

    - - - Посмотреть проект +
    + + +
    +
    +

    {{ feedItem.name }}

    + +
    + +
    + @if (industryService.industries | async; as industries) { +

    + @if (industryService.getIndustry(industries, feedItem.industry); as industry) { + + {{ industry.name }} + + } +

    + } + +
    + +

    {{ 25 }}

    + +
    +
    + +

    {{ feedItem.shortDescription }}

    +
    +
    + +
    + поддержать проект + + перейти в проект +
    diff --git a/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.scss b/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.scss index 0e0576ac7..2f662625d 100644 --- a/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.scss +++ b/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.scss @@ -2,48 +2,58 @@ @use "styles/responsive"; .card { - display: flex; - flex-direction: column; - align-items: center; - padding: 20px; - background-color: var(--white); - border: 1px solid var(--medium-grey-for-outline); + color: var(--light-white); + background-color: var(--accent); border-radius: var(--rounded-xl); + padding: 37px 24px 24px 24px; - &__title { + &__avatar { display: flex; - flex-direction: column; - gap: 4px; align-items: center; text-align: center; + } - @include typography.bold-body-14; + &__project { + display: grid; + grid-template-columns: 2fr 3fr; + grid-gap: 40px; + margin-bottom: 20px; + } - @include responsive.apply-desktop { - @include typography.heading-3; + &__info { + display: flex; + flex-direction: column; + + &:first-child { + margin-bottom: 4px; } - } - &__description { - margin-bottom: 10px; - color: var(--dark-grey); + &--main, &--additional { + display: flex; + align-items: center; + justify-content: space-between; + } } - &__avatar { - margin: 15px 0; + &__industry { + display: inline-flex; } - &__text { - margin-bottom: 20px; - color: var(--black); - - @include typography.body-12; + &__actions { + display: flex; + align-items: center; + width: 100%; + gap: 10px; + } - @include responsive.apply-desktop { - max-width: 580px; - margin-bottom: 5px; + &__score { + display: flex; + align-items: center; + gap: 3px; + } - @include typography.body-14; - } + &__description { + margin-top: 7px; + color: var(--white); } } diff --git a/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.ts b/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.ts index 5d5103ec6..16a0741f1 100644 --- a/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.ts +++ b/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.ts @@ -2,10 +2,13 @@ import { Component, Input } from "@angular/core"; import { CommonModule } from "@angular/common"; -import { ButtonComponent } from "@ui/components"; +import { ButtonComponent, IconComponent } from "@ui/components"; import { AvatarComponent } from "@ui/components/avatar/avatar.component"; import { Router, RouterLink } from "@angular/router"; import { FeedProject } from "@office/feed/models/feed-item.model"; +import { DayjsPipe } from "@corelib"; +import { IndustryService } from "@office/services/industry.service"; +import { TagComponent } from "@ui/components/tag/tag.component"; /** * КОМПОНЕНТ НОВОГО ПРОЕКТА @@ -35,36 +38,27 @@ import { FeedProject } from "@office/feed/models/feed-item.model"; @Component({ selector: "app-new-project", standalone: true, - imports: [CommonModule, ButtonComponent, AvatarComponent, RouterLink], + imports: [ + CommonModule, + ButtonComponent, + AvatarComponent, + RouterLink, + DayjsPipe, + IconComponent, + TagComponent, + ], templateUrl: "./new-project.component.html", styleUrl: "./new-project.component.scss", }) export class NewProjectComponent { - /** - * ВХОДНЫЕ ДАННЫЕ - * - * @Input feedItem - объект проекта для отображения - * - * СОДЕРЖИТ: - * - id: уникальный идентификатор проекта - * - name: название проекта - * - shortDescription: краткое описание проекта - * - industry: ID отрасли проекта - * - imageAddress: URL изображения проекта - * - viewsCount: количество просмотров проекта - * - leader: ID руководителя проекта - */ @Input() feedItem!: FeedProject; /** - * КОНСТРУКТОР * - * ЧТО ПРИНИМАЕТ: * @param router - сервис маршрутизации Angular для программной навигации * - * НАЗНАЧЕНИЕ: * Инициализирует компонент с доступом к сервису маршрутизации * для возможной навигации к детальной странице проекта */ - constructor(public readonly router: Router) {} + constructor(public readonly industryService: IndustryService) {} } diff --git a/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.html b/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.html index 8db4e8104..ebded859f 100644 --- a/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.html +++ b/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.html @@ -2,72 +2,109 @@ @if (feedItem) {
    - - newsItem.name -
    -
    {{ feedItem.project?.name }}
    -
    - {{ feedItem.datetimeCreated | dayjs: "format":"DD MMMM YYYY, HH:mm" }} -
    -
    -
    -

    - Тебя ищут в проект - {{ - feedItem.project?.name - }} -

    +
    + vacancy-card-background + @if (feedItem.project; as project) { +
    + -
    - @if (feedItem.requiredSkills?.length; as skillsLength) { -
    Необходимые навыки
    - @if (feedItem.requiredSkills; as requiredSkills) { @if (requiredSkills) { -
      - @for (skill of requiredSkills.slice(0, 8); track $index) { - {{ skill.name }} - } -
    - } -
    - @if (requiredSkills) { -
      - @for (skill of requiredSkills.slice(8); track $index) { - {{ skill.name }} +

      {{ project.name }}

      + + @if (industryService.industries | async; as industries) { +

      + @if (industryService.getIndustry(industries, project.industry); as industry) { + + {{ industry.name }} + } -

    +

    }
    - } @if (skillsLength > 8) { -
    - {{ readFullSkills ? "Скрыть" : "Читать полностью" }} -
    - } } -
    + } @if (feedItem; as vacancy) { +
    +
    +

    {{ vacancy.role }}

    +

    {{ vacancy.datetimeCreated | dayjs: "format":"DD MM YY" }}

    +
    + +
    + @if (vacancy.requiredSkills?.length; as skillsLength) { @if (vacancy.requiredSkills; as + requiredSkills) { @if (requiredSkills) { +
      + @for (skill of requiredSkills.slice(0, 3); track $index) { + {{ skill.name }} + } +
    + } +
    + @if (requiredSkills) { +
      + @for (skill of requiredSkills.slice(8); track $index) { + {{ skill.name }} + } +
    + } +
    + } @if (skillsLength > 8) { +
    + {{ readFullSkills ? "Скрыть" : "Читать полностью" }} +
    + } } +
    - @if (feedItem.description) { -
    -
    -

    - @if (descriptionExpandable) { -
    - {{ readFullDescription ? "Скрыть" : "Читать полностью" }} + @if (feedItem.description) { +
    +
    +

    + @if (descriptionExpandable) { +
    + {{ readFullDescription ? "Скрыть" : "Читать полностью" }} +
    + } +
    }
    + }
    - } -
    - {{ feedItem.role }} + + @if (feedItem) { +
    Откликнутьсяперейти в проект + откликнуться на вакансию
    + }
    } diff --git a/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.scss b/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.scss index f7501580b..554cd6c8c 100644 --- a/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.scss +++ b/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.scss @@ -30,23 +30,30 @@ } .card { - padding: 20px; - background-color: var(--white); - border: 1px solid var(--medium-grey-for-outline); + padding: 27px 24px 24px 24px; + background-color: var(--light-white); + border: 0.5px solid var(--medium-grey-for-outline); border-radius: var(--rounded-xl); + position: relative; + z-index: 100; + overflow-y: hidden; - &__head { - margin-bottom: 10px; + &__inner { + display: grid; + grid-template-columns: 2fr 3fr; + align-items: flex-start; + gap: 10px; } - &__title { - display: flex; - flex-direction: column; - gap: 5px; - align-items: center; - margin-bottom: 10px; + &__background { + position: absolute; + top: 0; + left: 0; + z-index: 0; + } - @include typography.bold-body-16; + &__industry { + display: inline-flex; } &__description { @@ -54,8 +61,59 @@ white-space: pre-wrap; } - &__action { - margin-top: 20px; + &__project { + display: flex; + flex-direction: column; + gap: 6px; + align-items: center; + text-align: center; + } + + &__vacancy { + display: flex; + flex-direction: column; + gap: 8px; + } + + &__actions { + display: flex; + align-items: center; + gap: 10px; + margin-top: 15px; + + &:last-child { + ::ng-deep { + app-button { + .button--big { + padding: 4px 19px; + } + } + } + } + } +} + +.lists { + &__section { + display: flex; + justify-content: space-between; + margin-bottom: 8px; + border-bottom: 0.5px solid var(--accent); + } + + &__icon { + color: var(--accent); + } + + &__title { + margin-bottom: 8px; + color: var(--accent); + } + + &__item { + display: flex; + gap: 6px; + align-items: center; } } @@ -108,51 +166,12 @@ &:hover { color: var(--accent-dark); } - - @include typography.body-14; -} - -.head { - display: flex; - align-items: center; - - &__avatar { - width: 40px; - height: 40px; - margin-right: 10px; - border-radius: 50%; - object-fit: cover; - } - - &__name { - max-width: 200px; - overflow: hidden; - color: var(--black); - text-overflow: ellipsis; - white-space: nowrap; - - @include typography.bold-body-14; - - @include responsive.apply-desktop { - @include typography.bold-body-16; - } - } - - &__date { - color: var(--dark-grey); - } } .skills { &__title { margin-bottom: 5px; color: var(--dark-grey); - - @include typography.body-14; - - @include responsive.apply-desktop { - margin-bottom: 10px; - } } &__list { @@ -169,36 +188,3 @@ @include expandable-list; } - -.action { - @include responsive.apply-desktop { - display: flex; - align-items: center; - justify-content: space-between; - padding: 10px 20px; - border: 1px solid var(--medium-grey-for-outline); - border-radius: var(--rounded-xl); - } - - &__job { - display: block; - padding: 20px 0; - margin-bottom: 15px; - text-align: center; - cursor: pointer; - border: 1px solid var(--medium-grey-for-outline); - border-radius: var(--rounded-xl); - - @include typography.bold-body-14; - - @include responsive.apply-desktop { - padding: 0; - margin-bottom: 0; - border: none; - } - } - - &__button { - width: 150px; - } -} diff --git a/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.ts b/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.ts index fef15eab3..b5e63b150 100644 --- a/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.ts +++ b/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.ts @@ -15,26 +15,25 @@ import { Router, RouterLink } from "@angular/router"; import { TagComponent } from "@ui/components/tag/tag.component"; import { Vacancy } from "@models/vacancy.model"; import { expandElement } from "@utils/expand-element"; +import { IndustryService } from "@office/services/industry.service"; +import { AvatarComponent } from "@ui/components/avatar/avatar.component"; +import { AdvertCardComponent } from "@office/shared/advert-card/advert-card.component"; /** - * КОМПОНЕНТ ОТКРЫТОЙ ВАКАНСИИ * * Отображает карточку активной вакансии в ленте новостей с полным функционалом. * Поддерживает развертывание/свертывание длинного контента и интерактивные элементы. * - * ОСНОВНЫЕ ФУНКЦИИ: * - Отображение полной информации о вакансии * - Развертывание/свертывание описания и списка навыков * - Навигация к детальной странице вакансии * - Форматирование текста с поддержкой ссылок и переносов строк * - Отображение тегов и навыков * - * ИНТЕРАКТИВНЫЕ ЭЛЕМЕНТЫ: * - Кнопки "Показать полностью" / "Свернуть" * - Теги навыков и требований * - Ссылки на детальную страницу * - * ИСПОЛЬЗУЕМЫЕ ПАЙПЫ: * - DayjsPipe: форматирование дат * - ParseLinksPipe: преобразование ссылок в кликабельные элементы * - ParseBreaksPipe: обработка переносов строк @@ -50,21 +49,16 @@ import { expandElement } from "@utils/expand-element"; DayjsPipe, ParseLinksPipe, ParseBreaksPipe, + AvatarComponent, + AdvertCardComponent, ], templateUrl: "./open-vacancy.component.html", styleUrl: "./open-vacancy.component.scss", }) export class OpenVacancyComponent implements AfterViewInit { - /** - * ВХОДНЫЕ ДАННЫЕ - * - * @Input feedItem - объект вакансии для отображения - * Содержит всю информацию о вакансии: название, описание, требования, навыки и т.д. - */ @Input() feedItem!: Vacancy; /** - * ССЫЛКИ НА DOM ЭЛЕМЕНТЫ * * @ViewChild skillsEl - ссылка на элемент со списком навыков * @ViewChild descEl - ссылка на элемент с описанием вакансии @@ -74,17 +68,12 @@ export class OpenVacancyComponent implements AfterViewInit { @ViewChild("skillsEl") skillsEl?: ElementRef; @ViewChild("descEl") descEl?: ElementRef; - constructor(public readonly router: Router, private readonly cdRef: ChangeDetectorRef) {} + constructor( + public readonly router: Router, + private readonly cdRef: ChangeDetectorRef, + public readonly industryService: IndustryService + ) {} - /** - * ИНИЦИАЛИЗАЦИЯ ПОСЛЕ ОТРИСОВКИ - * - * ЧТО ДЕЛАЕТ: - * - Проверяет, нужны ли кнопки развертывания для описания и навыков - * - Сравнивает высоту контента с высотой контейнера - * - Устанавливает флаги для показа кнопок "Показать полностью" - * - Запускает обнаружение изменений для обновления UI - */ ngAfterViewInit(): void { // Проверяем, превышает ли описание доступную высоту const descElement = this.descEl?.nativeElement; @@ -107,14 +96,11 @@ export class OpenVacancyComponent implements AfterViewInit { readFullSkills = false; // Развернут ли список навыков /** - * РАЗВЕРТЫВАНИЕ/СВЕРТЫВАНИЕ ОПИСАНИЯ * - * ЧТО ПРИНИМАЕТ: * @param elem - DOM элемент для анимации * @param expandedClass - CSS класс для развернутого состояния * @param isExpanded - текущее состояние (развернуто/свернуто) * - * ЧТО ДЕЛАЕТ: * - Переключает визуальное состояние описания * - Применяет анимацию развертывания/свертывания * - Обновляет флаг состояния @@ -125,9 +111,7 @@ export class OpenVacancyComponent implements AfterViewInit { } /** - * РАЗВЕРТЫВАНИЕ/СВЕРТЫВАНИЕ СПИСКА НАВЫКОВ * - * ЧТО ПРИНИМАЕТ: * @param elem - DOM элемент для анимации * @param expandedClass - CSS класс для развернутого состояния * @param isExpanded - текущее состояние (развернуто/свернуто) diff --git a/projects/social_platform/src/app/office/office.component.ts b/projects/social_platform/src/app/office/office.component.ts index 473f95d56..3c82830dd 100644 --- a/projects/social_platform/src/app/office/office.component.ts +++ b/projects/social_platform/src/app/office/office.component.ts @@ -59,12 +59,37 @@ export class OfficeComponent implements OnInit, OnDestroy { public readonly chatService: ChatService ) {} + invites: Signal = toSignal( + this.route.data.pipe( + map(r => r["invites"]), + map(invites => invites.filter((invite: Invite) => invite.isAccepted === null)) + ) + ); + + profile?: User; + + waitVerificationModal = false; + waitVerificationAccepted = false; + + inviteErrorModal = false; + + navItems: { + name: string; + icon: string; + link: string; + isExternal?: boolean; + isActive?: boolean; + }[] = []; + + subscriptions$: Subscription[] = []; + ngOnInit(): void { const globalSubscription$ = forkJoin([this.industryService.getAll()]).subscribe(noop); this.subscriptions$.push(globalSubscription$); const profileSub$ = this.authService.profile.subscribe(profile => { this.profile = profile; + this.buildNavItems(profile); if (!this.profile.doesCompleted()) { this.router @@ -102,32 +127,6 @@ export class OfficeComponent implements OnInit, OnDestroy { this.subscriptions$.forEach($ => $.unsubscribe()); } - invites: Signal = toSignal( - this.route.data.pipe( - map(r => r["invites"]), - map(invites => invites.filter((invite: Invite) => invite.isAccepted === null)) - ) - ); - - navItems = [ - { name: "новости", icon: "feed", link: "feed" }, - { name: "проекты", icon: "projects", link: "projects" }, - { name: "участники", icon: "people-bold", link: "members" }, - { name: "программы", icon: "program", link: "program" }, - { name: "вакансии", icon: "search-sidebar", link: "vacancies" }, - { name: "траектории", icon: "trajectories", link: "skills", isExternal: true, isActive: false }, - { name: "чаты", icon: "message", link: "chats" }, - ]; - - subscriptions$: Subscription[] = []; - - waitVerificationModal = false; - waitVerificationAccepted = false; - - inviteErrorModal = false; - - profile?: User; - onAcceptWaitVerification() { this.waitVerificationAccepted = true; localStorage.setItem("waitVerificationAccepted", "true"); @@ -171,4 +170,23 @@ export class OfficeComponent implements OnInit, OnDestroy { .then(() => console.debug("Route changed from OfficeComponent")) ); } + + private buildNavItems(profile: User) { + this.navItems = [ + { name: "мой профиль", icon: "person", link: `profile/${profile.id}` }, + { name: "новости", icon: "feed", link: "feed" }, + { name: "проекты", icon: "projects", link: "projects" }, + { name: "участники", icon: "people-bold", link: "members" }, + { name: "программы", icon: "program", link: "program" }, + { name: "вакансии", icon: "search-sidebar", link: "vacancies" }, + { + name: "траектории", + icon: "trajectories", + link: "skills", + isExternal: true, + isActive: false, + }, + { name: "чаты", icon: "message", link: "chats" }, + ]; + } } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.ts b/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.ts index dbdd57b5b..e9e623805 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.ts +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.ts @@ -14,6 +14,7 @@ import { TextareaComponent } from "@ui/components/textarea/textarea.component"; import { ProjectService } from "@office/services/project.service"; import { generateOptionsList, optionsListElement } from "@utils/generate-options-list"; import { Partner } from "@office/models/partner.model"; +import { resourceOptionsList } from "projects/core/src/consts/resource-options-list"; @Component({ selector: "app-project-partner-resources-step", @@ -119,30 +120,7 @@ export class ProjectPartnerResourcesStepComponent implements OnDestroy { } get resourcesTypeOptions(): optionsListElement[] { - const resourceOptions = [ - { - id: 1, - value: "infrastructure", - label: "Инфраструктурный", - }, - { - id: 2, - value: "staff", - label: "Кадровый", - }, - { - id: 3, - value: "financial", - label: "Финансовый", - }, - { - id: 4, - value: "information", - label: "Информационный", - }, - ]; - - return resourceOptions; + return resourceOptionsList; } /** diff --git a/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.scss b/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.scss index 62dc01b7b..0d8c8d895 100644 --- a/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.scss +++ b/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.scss @@ -11,7 +11,6 @@ gap: 16px; align-items: center; width: 100%; - border: 0.5px solid var(--medium-grey-for-outline); @include responsive.apply-desktop { display: flex; diff --git a/projects/social_platform/src/app/ui/components/button/button.component.scss b/projects/social_platform/src/app/ui/components/button/button.component.scss index 3739b14f9..f63d38c95 100644 --- a/projects/social_platform/src/app/ui/components/button/button.component.scss +++ b/projects/social_platform/src/app/ui/components/button/button.component.scss @@ -95,6 +95,12 @@ } } + &.button--white { + color: var(--white); + border: 0.5px solid var(--white); + background: transparent; + } + &.button--no-border { border: none; } diff --git a/projects/social_platform/src/app/ui/components/snackbar/snackbar.component.scss b/projects/social_platform/src/app/ui/components/snackbar/snackbar.component.scss index abbf125c3..d274c275d 100644 --- a/projects/social_platform/src/app/ui/components/snackbar/snackbar.component.scss +++ b/projects/social_platform/src/app/ui/components/snackbar/snackbar.component.scss @@ -4,7 +4,7 @@ position: absolute; top: 0; right: 0; - z-index: 20; + z-index: 2000; padding-top: 50px; padding-right: 50px; overflow-x: hidden; diff --git a/projects/social_platform/src/app/utils/generate-year-list.ts b/projects/social_platform/src/app/utils/generate-year-list.ts deleted file mode 100644 index 597bbb1ca..000000000 --- a/projects/social_platform/src/app/utils/generate-year-list.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** @format */ - -interface yearListElement { - id: number; // порядковый номер в массиве - value: string; // строка, которую будем показывать в UI - label: string; // то же самое, что и value (можно использовать обе подписи) -} - -/** - * Генерирует массив годов. - * - * @param amount – сколько лет нужно вывести. - * Считаем, что список должен начинаться с «текущий‑year‑amount+1» и - * заканчиваться текущим календарным годом. - * @returns массив объектов вида { id, value, label } - * - * Пример: generateYearList(3) (при текущем 2025‑м году) → - * [ - * { id: 0, value: '2023 год', label: '2023 год' }, - * { id: 1, value: '2024 год', label: '2024 год' }, - * { id: 2, value: '2025 год', label: '2025 год' } - * ] - */ -export const generateYearList = (amount: number): yearListElement[] => { - if (amount <= 0) return []; - - const now = new Date().getFullYear(); - const firstYear = now - amount + 1; - const list: yearListElement[] = []; - - for (let i = 0; i < amount; i++) { - const year = firstYear + i; - list.push({ - id: i, - value: `${year} год`, - label: `${year} год`, - }); - } - - const currentId = amount - 1; - list.push({ - id: currentId, - value: `${now} год`, - label: "по наст. вр.", - }); - - return list; -}; diff --git a/projects/social_platform/src/app/utils/inviteToProjectMapper.ts b/projects/social_platform/src/app/utils/inviteToProjectMapper.ts index 39cb4fc41..94c55eb61 100644 --- a/projects/social_platform/src/app/utils/inviteToProjectMapper.ts +++ b/projects/social_platform/src/app/utils/inviteToProjectMapper.ts @@ -1,7 +1,6 @@ /** @format */ import { Invite } from "@office/models/invite.model"; -import { Project } from "@office/models/project.model"; export const inviteToProjectMapper = (invites: Invite[] = []): any[] => { return (invites ?? []).map((invite: Invite) => ({ diff --git a/projects/social_platform/src/assets/icons/svg/comment.svg b/projects/social_platform/src/assets/icons/svg/comment.svg new file mode 100644 index 000000000..6f6918bcc --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/comment.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/svg/people-bold.svg b/projects/social_platform/src/assets/icons/svg/people-bold.svg index af2ee0330..cea420abc 100644 --- a/projects/social_platform/src/assets/icons/svg/people-bold.svg +++ b/projects/social_platform/src/assets/icons/svg/people-bold.svg @@ -1,3 +1,3 @@ - - + + diff --git a/projects/social_platform/src/assets/icons/svg/procollab.svg b/projects/social_platform/src/assets/icons/svg/procollab.svg new file mode 100644 index 000000000..036b53555 --- /dev/null +++ b/projects/social_platform/src/assets/icons/svg/procollab.svg @@ -0,0 +1,3 @@ + + + diff --git a/projects/social_platform/src/assets/icons/svg/share.svg b/projects/social_platform/src/assets/icons/svg/share.svg index dc8f021ca..d27c754aa 100644 --- a/projects/social_platform/src/assets/icons/svg/share.svg +++ b/projects/social_platform/src/assets/icons/svg/share.svg @@ -1,3 +1,3 @@ - - + + diff --git a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg index 655a8efe7..1937c5c01 100644 --- a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg +++ b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/projects/social_platform/src/assets/images/office/feed/vacancy-card/vacancies-card-background.svg b/projects/social_platform/src/assets/images/office/feed/vacancy-card/vacancies-card-background.svg new file mode 100644 index 000000000..d2249ceb2 --- /dev/null +++ b/projects/social_platform/src/assets/images/office/feed/vacancy-card/vacancies-card-background.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From f98ec9b6b7a8b984e12a8023807f7787cb97f10c Mon Sep 17 00:00:00 2001 From: Awakich Date: Tue, 21 Oct 2025 00:38:48 +0300 Subject: [PATCH 107/126] add styles for feed, open-vacancy, new-project component & news-card, feed-filter --- .../news-card/news-card.component.scss | 4 +-- .../src/app/office/feed/feed.component.scss | 6 ++-- .../feed/filter/feed-filter.component.scss | 28 +++++++++---------- .../new-project/new-project.component.scss | 9 +++--- .../open-vacancy/open-vacancy.component.scss | 12 ++++---- .../components/button/button.component.scss | 2 +- 6 files changed, 30 insertions(+), 31 deletions(-) diff --git a/projects/social_platform/src/app/office/features/news-card/news-card.component.scss b/projects/social_platform/src/app/office/features/news-card/news-card.component.scss index 39c6ad81d..d449adec1 100644 --- a/projects/social_platform/src/app/office/features/news-card/news-card.component.scss +++ b/projects/social_platform/src/app/office/features/news-card/news-card.component.scss @@ -76,8 +76,8 @@ /* stylelint-disable value-no-vendor-prefix */ &__text { - white-space: break-spaces; color: var(--grey-for-text); + white-space: break-spaces; p { display: -webkit-box; @@ -161,8 +161,8 @@ &__left { display: flex; - align-items: center; gap: 10px; + align-items: center; } &__item { diff --git a/projects/social_platform/src/app/office/feed/feed.component.scss b/projects/social_platform/src/app/office/feed/feed.component.scss index c22a99700..48137976f 100644 --- a/projects/social_platform/src/app/office/feed/feed.component.scss +++ b/projects/social_platform/src/app/office/feed/feed.component.scss @@ -11,9 +11,9 @@ } &__item { - break-inside: avoid; - margin-bottom: 20px; - width: 100%; display: block; + width: 100%; + margin-bottom: 20px; + break-inside: avoid; } } diff --git a/projects/social_platform/src/app/office/feed/filter/feed-filter.component.scss b/projects/social_platform/src/app/office/feed/filter/feed-filter.component.scss index fdce0eda3..335b07c0e 100644 --- a/projects/social_platform/src/app/office/feed/filter/feed-filter.component.scss +++ b/projects/social_platform/src/app/office/feed/filter/feed-filter.component.scss @@ -75,26 +75,26 @@ .filter { display: flex; + gap: 20px; align-items: center; justify-content: space-between; - gap: 20px; &__option { - cursor: pointer; + position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; + height: 66px; + padding: 40px 24px 12px; + cursor: pointer; background-color: var(--light-white); - border-radius: var(--rounded-xl); border: 0.5px solid var(--medium-grey-for-outline); - padding: 40px 24px 12px 24px; - height: 66px; - position: relative; + border-radius: var(--rounded-xl); &--disable { - opacity: 0.5; cursor: not-allowed; + opacity: 0.5; } &--active { @@ -108,18 +108,18 @@ } &--icon { + position: absolute; + bottom: 100%; + left: 50%; display: flex; align-items: center; justify-content: center; width: 50px; height: 50px; - border-radius: 50%; - position: absolute; - bottom: 100%; - left: 50%; - transform: translate(-50%, 50%); background-color: var(--light-white); border: 0.5px solid var(--medium-grey-for-outline); + border-radius: 50%; + transform: translate(-50%, 50%); i { color: var(--accent); @@ -127,13 +127,11 @@ } p { - text-align: center; color: var(--grey-for-text); + text-align: center; } } - - span { display: block; margin-bottom: 6px; diff --git a/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.scss b/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.scss index 2f662625d..31483d83b 100644 --- a/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.scss +++ b/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.scss @@ -2,10 +2,10 @@ @use "styles/responsive"; .card { + padding: 37px 24px 24px; color: var(--light-white); background-color: var(--accent); border-radius: var(--rounded-xl); - padding: 37px 24px 24px 24px; &__avatar { display: flex; @@ -28,7 +28,8 @@ margin-bottom: 4px; } - &--main, &--additional { + &--main, + &--additional { display: flex; align-items: center; justify-content: space-between; @@ -41,15 +42,15 @@ &__actions { display: flex; + gap: 10px; align-items: center; width: 100%; - gap: 10px; } &__score { display: flex; - align-items: center; gap: 3px; + align-items: center; } &__description { diff --git a/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.scss b/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.scss index 554cd6c8c..e0c696fc7 100644 --- a/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.scss +++ b/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.scss @@ -30,19 +30,19 @@ } .card { - padding: 27px 24px 24px 24px; - background-color: var(--light-white); - border: 0.5px solid var(--medium-grey-for-outline); - border-radius: var(--rounded-xl); position: relative; z-index: 100; + padding: 27px 24px 24px; overflow-y: hidden; + background-color: var(--light-white); + border: 0.5px solid var(--medium-grey-for-outline); + border-radius: var(--rounded-xl); &__inner { display: grid; grid-template-columns: 2fr 3fr; - align-items: flex-start; gap: 10px; + align-items: flex-start; } &__background { @@ -77,8 +77,8 @@ &__actions { display: flex; - align-items: center; gap: 10px; + align-items: center; margin-top: 15px; &:last-child { diff --git a/projects/social_platform/src/app/ui/components/button/button.component.scss b/projects/social_platform/src/app/ui/components/button/button.component.scss index f63d38c95..01f70b2c2 100644 --- a/projects/social_platform/src/app/ui/components/button/button.component.scss +++ b/projects/social_platform/src/app/ui/components/button/button.component.scss @@ -97,8 +97,8 @@ &.button--white { color: var(--white); - border: 0.5px solid var(--white); background: transparent; + border: 0.5px solid var(--white); } &.button--no-border { From d64a8cc8dcc420a16175dcda02cfb2df611dc798 Mon Sep 17 00:00:00 2001 From: Awakich Date: Tue, 21 Oct 2025 17:29:33 +0300 Subject: [PATCH 108/126] fix chat, chat-direct, change design of margins in project, add validator to date in edit project, modals for leave project --- .../chat-direct/chat-direct.component.html | 2 +- .../chat-direct/chat-direct.component.ts | 3 +- .../src/app/office/chat/chat.component.html | 4 +- .../src/app/office/chat/chat.component.ts | 4 +- .../features/detail/detail.component.html | 25 ++++--- .../features/detail/detail.component.scss | 30 +++----- .../response-card.component.html | 28 -------- .../vacancy-card/vacancy-card.component.html | 4 +- .../new-project/new-project.component.html | 6 +- .../filters/members-filters.component.scss | 2 +- .../app/office/members/members.component.html | 2 +- .../profile/detail/main/main.component.html | 1 + .../profile/detail/main/main.component.ts | 3 +- .../office/profile/edit/edit.component.html | 69 ++++++++++--------- .../office/profile/edit/edit.component.scss | 5 +- .../app/office/profile/edit/edit.component.ts | 21 +++++- .../office/program/main/main.component.scss | 4 +- .../projects/detail/info/info.component.html | 1 + .../projects/detail/info/info.component.scss | 2 +- .../projects/detail/info/info.component.ts | 3 +- .../project-direction-card.component.html | 28 ++++++++ .../project-direction-card.component.scss | 28 ++++++++ .../project-direction-card.component.ts | 6 +- .../office/projects/edit/edit.component.scss | 2 +- .../office/projects/edit/edit.component.ts | 30 +++++--- .../edit/services/project-form.service.ts | 11 ++- .../project-achievement-step.component.html | 3 +- .../project-achievement-step.component.ts | 11 ++- .../project-main-step.component.html | 6 +- ...ject-partner-resources-step.component.scss | 2 +- ...roject-partner-resources-step.component.ts | 3 +- .../project-vacancy-step.component.html | 24 +------ .../project-vacancy-step.component.scss | 4 +- .../office/projects/projects.component.html | 2 +- .../office/projects/projects.component.scss | 3 +- .../vacancies/detail/info/info.component.html | 7 +- .../vacancies/detail/info/info.component.ts | 2 - .../filter/vacancy-filter.component.html | 2 +- .../filter/vacancy-filter.component.scss | 12 ++-- .../ui/components/input/input.component.html | 3 +- .../ui/components/input/input.component.ts | 45 +----------- .../app/utils/helpers/directionItemBuilder.ts | 5 +- 42 files changed, 239 insertions(+), 219 deletions(-) diff --git a/projects/social_platform/src/app/office/chat/chat-direct/chat-direct/chat-direct.component.html b/projects/social_platform/src/app/office/chat/chat-direct/chat-direct/chat-direct.component.html index d93b657f2..1172d12a8 100644 --- a/projects/social_platform/src/app/office/chat/chat-direct/chat-direct/chat-direct.component.html +++ b/projects/social_platform/src/app/office/chat/chat-direct/chat-direct/chat-direct.component.html @@ -1,7 +1,7 @@
    - +
    @if (chat) { diff --git a/projects/social_platform/src/app/office/chat/chat-direct/chat-direct/chat-direct.component.ts b/projects/social_platform/src/app/office/chat/chat-direct/chat-direct/chat-direct.component.ts index 14674efdf..c570f6086 100644 --- a/projects/social_platform/src/app/office/chat/chat-direct/chat-direct/chat-direct.component.ts +++ b/projects/social_platform/src/app/office/chat/chat-direct/chat-direct/chat-direct.component.ts @@ -12,6 +12,7 @@ import { AuthService } from "@auth/services"; import { AvatarComponent } from "@ui/components/avatar/avatar.component"; import { ApiPagination } from "@models/api-pagination.model"; import { BarComponent } from "@ui/components"; +import { BackComponent } from "@uilib"; /** * Компонент для отображения конкретного прямого чата @@ -32,7 +33,7 @@ import { BarComponent } from "@ui/components"; templateUrl: "./chat-direct.component.html", styleUrl: "./chat-direct.component.scss", standalone: true, - imports: [RouterLink, AvatarComponent, ChatWindowComponent, BarComponent], + imports: [RouterLink, AvatarComponent, ChatWindowComponent, BarComponent, BackComponent], }) export class ChatDirectComponent implements OnInit, OnDestroy { constructor( diff --git a/projects/social_platform/src/app/office/chat/chat.component.html b/projects/social_platform/src/app/office/chat/chat.component.html index de236f37c..7fc7bacef 100644 --- a/projects/social_platform/src/app/office/chat/chat.component.html +++ b/projects/social_platform/src/app/office/chat/chat.component.html @@ -2,9 +2,11 @@
    + + -
    -

    Выход из проекта

    - leave image -

    - Ты уверен, что хочешь выйти из проекта? Это действие нельзя будет отменить. -

    -
    +
    +
    +

    + вы уверены, что хотите покинуть команду +

    + +
    + +
    - Да + покинуть команду + - Нет, хочу остаться + остаться
    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 933553fda..dcfd81f42 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 @@ -241,6 +241,7 @@ $detail-bar-mb: 12px; } &__title { + display: flex; text-align: center; } @@ -251,6 +252,13 @@ $detail-bar-mb: 12px; text-align: center; } + &__buttons { + display: flex; + gap: 10px; + align-items: center; + margin-top: 20px; + } + &__button { margin-top: 20px; } @@ -317,25 +325,3 @@ $detail-bar-mb: 12px; white-space: nowrap; } } - -.unsubscribe-modal { - display: flex; - flex-direction: column; - align-items: center; - padding: 24px; - - h3 { - margin-bottom: 16px; - color: var(--black); - } - - img { - margin-bottom: 16px; - } - - &__buttons { - display: flex; - gap: 12px; - align-items: center; - } -} diff --git a/projects/social_platform/src/app/office/features/response-card/response-card.component.html b/projects/social_platform/src/app/office/features/response-card/response-card.component.html index a4ae338b6..0ad2e9c8b 100644 --- a/projects/social_platform/src/app/office/features/response-card/response-card.component.html +++ b/projects/social_platform/src/app/office/features/response-card/response-card.component.html @@ -20,34 +20,6 @@ [type]="response.accompanyingFile.mimeType" [size]="response.accompanyingFile.size" > - } @if (!(profileId === response.user.id)) { -
    - - связаться - - принять - - отклонить - -
    }
    diff --git a/projects/social_platform/src/app/office/features/vacancy-card/vacancy-card.component.html b/projects/social_platform/src/app/office/features/vacancy-card/vacancy-card.component.html index 2e3cdc373..575356585 100644 --- a/projects/social_platform/src/app/office/features/vacancy-card/vacancy-card.component.html +++ b/projects/social_platform/src/app/office/features/vacancy-card/vacancy-card.component.html @@ -27,11 +27,11 @@

    {{ vacancy.role }}

    - + - +
    diff --git a/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.html b/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.html index 53d887588..f6b9db800 100644 --- a/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.html +++ b/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.html @@ -26,11 +26,11 @@

    } -
    - +

    {{ feedItem.shortDescription }}

    diff --git a/projects/social_platform/src/app/office/members/filters/members-filters.component.scss b/projects/social_platform/src/app/office/members/filters/members-filters.component.scss index c4fdb9313..cba283cf1 100644 --- a/projects/social_platform/src/app/office/members/filters/members-filters.component.scss +++ b/projects/social_platform/src/app/office/members/filters/members-filters.component.scss @@ -26,7 +26,7 @@ margin-bottom: 0; @include responsive.apply-desktop { - margin-bottom: 10px; + margin-bottom: 14px; } .filter__name { diff --git a/projects/social_platform/src/app/office/members/members.component.html b/projects/social_platform/src/app/office/members/members.component.html index dc0fbf4a1..b07269c27 100644 --- a/projects/social_platform/src/app/office/members/members.component.html +++ b/projects/social_platform/src/app/office/members/members.component.html @@ -1,7 +1,7 @@
    - +
    diff --git a/projects/social_platform/src/app/office/profile/detail/main/main.component.html b/projects/social_platform/src/app/office/profile/detail/main/main.component.html index f9316ca0d..764ff4060 100644 --- a/projects/social_platform/src/app/office/profile/detail/main/main.component.html +++ b/projects/social_platform/src/app/office/profile/detail/main/main.component.html @@ -113,6 +113,7 @@

    обо мне

    [direction]="directionItem.direction" [icon]="directionItem.icon" [about]="directionItem.about" + [type]="directionItem.type" > }
    diff --git a/projects/social_platform/src/app/office/profile/detail/main/main.component.ts b/projects/social_platform/src/app/office/profile/detail/main/main.component.ts index d0a08aa0c..e67f57bb0 100644 --- a/projects/social_platform/src/app/office/profile/detail/main/main.component.ts +++ b/projects/social_platform/src/app/office/profile/detail/main/main.component.ts @@ -111,7 +111,8 @@ export class ProfileMainComponent implements OnInit, AfterViewInit, OnDestroy { 2, ["навыки", "достижения"], ["squiz", "medal"], - ["", ""] + [user.skills, ""], + ["array", "string"] )!; } this.user = user as User; diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.html b/projects/social_platform/src/app/office/profile/edit/edit.component.html index ea78570fc..bd4547840 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.html +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.html @@ -482,7 +482,7 @@

    редактирование профиля

    {{ educationItem.organizationName }}

    -

    +

    @if(educationItem.entryYear && educationItem.completionYear) { {{ educationItem.entryYear }} год • {{ educationItem.completionYear }} год } @else if (educationItem.entryYear && !educationItem.completionYear) { @@ -492,15 +492,15 @@

    редактирование профиля

    -

    +

    {{ educationItem.description }}

    -

    +

    {{ educationItem.educationLevel }}

    -

    +

    {{ educationItem.educationStatus }}

    @@ -656,7 +656,7 @@

    редактирование профиля

    {{ workItem.organizationName }}

    -

    +

    @if(workItem.entryYear && workItem.completionYear) { {{ workItem.entryYear }} год • {{ workItem.completionYear }} год } @else if (workItem.entryYear && !workItem.completionYear) { {{ workItem.entryYear }} год } @@ -666,11 +666,11 @@

    редактирование профиля

    -

    +

    {{ workItem.description }}

    -

    +

    {{ workItem.jobPosition }}

    @@ -696,7 +696,7 @@

    редактирование профиля

    } @if(editingStep === 'achievements'){ - +
    } @if (editingStep === 'skills') {
    @@ -834,6 +830,7 @@

    редактирование профиля

    + @if (showLanguageFields) {
    @if (profileForm.get("language"); as language) {
    @@ -877,17 +874,19 @@

    редактирование профиля

    количество добавляемых языков не более 4-х + } @@ -904,7 +903,7 @@

    редактирование профиля

    {{ languageItem.language }}

    -
    +
    @@ -915,7 +914,9 @@

    редактирование профиля

    -

    {{ languageItem.languageLevel }}

    +

    + {{ languageItem.languageLevel }} +

    } }
    diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.scss b/projects/social_platform/src/app/office/profile/edit/edit.component.scss index 203e9f712..4175516aa 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.scss +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.scss @@ -11,7 +11,7 @@ &__top { position: sticky; - top: 10%; + top: -50%; left: 6%; z-index: 100; display: flex; @@ -180,6 +180,7 @@ &--icons { display: flex; gap: 10px; + flex-direction: column; align-items: center; } } @@ -257,6 +258,7 @@ width: 20px; height: 20px; padding: 6px; + cursor: pointer; border-radius: 50%; } @@ -287,6 +289,7 @@ &__text { margin-top: 10px; + color: var(--grey-for-text); } } diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.ts b/projects/social_platform/src/app/office/profile/edit/edit.component.ts index 2c85b77d4..a2466dd51 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.ts +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.ts @@ -350,6 +350,7 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { showEducationFields = false; showWorkFields = false; + showLanguageFields = false; selectedEntryYearEducationId = signal(undefined); selectedComplitionYearEducationId = signal(undefined); @@ -479,11 +480,19 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { profileFormSubmitting = false; profileForm: FormGroup; - addAchievement(id?: number, title?: string, status?: string): void { + addAchievement( + id?: number, + title?: string, + status?: string, + year?: number, + files?: string[] + ): void { this.achievements.push( this.fb.group({ title: [title ?? "", [Validators.required]], status: [status ?? "", [Validators.required]], + year: [year?.toString() ?? "", [Validators.required]], + files: [files ?? []], id: [id], }) ); @@ -754,6 +763,11 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { } addLanguage() { + if (!this.showLanguageFields) { + this.showLanguageFields = true; + return; + } + const languageValue = this.profileForm.get("language")?.value; const languageLevelValue = this.profileForm.get("languageLevel")?.value; @@ -805,13 +819,14 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { this.profileForm.get(name)?.markAsPristine(); this.profileForm.get(name)?.updateValueAndValidity(); }); - - this.editLanguageClick = false; + this.showLanguageFields = false; } + this.editLanguageClick = false; } editLanguage(index: number) { this.editLanguageClick = true; + this.showLanguageFields = true; const languageItem = this.userLanguages.value[index]; this.languageList.forEach(language => { diff --git a/projects/social_platform/src/app/office/program/main/main.component.scss b/projects/social_platform/src/app/office/program/main/main.component.scss index 4548ffcef..35315428d 100644 --- a/projects/social_platform/src/app/office/program/main/main.component.scss +++ b/projects/social_platform/src/app/office/program/main/main.component.scss @@ -1,13 +1,13 @@ .programs { display: grid; grid-template-columns: 8fr 2fr; - grid-gap: 20px; + grid-gap: 24px; justify-content: space-between; &__list { display: grid; grid-template-columns: 4fr 4fr; - grid-gap: 20px; + grid-gap: 24px; } &__filter { diff --git a/projects/social_platform/src/app/office/projects/detail/info/info.component.html b/projects/social_platform/src/app/office/projects/detail/info/info.component.html index 10c25101d..6b3661cc8 100644 --- a/projects/social_platform/src/app/office/projects/detail/info/info.component.html +++ b/projects/social_platform/src/app/office/projects/detail/info/info.component.html @@ -87,6 +87,7 @@

    о проекте

    [direction]="directionItem.direction" [icon]="directionItem.icon" [about]="directionItem.about" + [type]="directionItem.type" > }
    diff --git a/projects/social_platform/src/app/office/projects/detail/info/info.component.scss b/projects/social_platform/src/app/office/projects/detail/info/info.component.scss index 601e40ad9..1d26871b7 100644 --- a/projects/social_platform/src/app/office/projects/detail/info/info.component.scss +++ b/projects/social_platform/src/app/office/projects/detail/info/info.component.scss @@ -102,7 +102,7 @@ grid-template-columns: repeat(5, 1fr); grid-gap: 20px; align-items: center; - margin-top: 14px; + margin-top: 24px; } } diff --git a/projects/social_platform/src/app/office/projects/detail/info/info.component.ts b/projects/social_platform/src/app/office/projects/detail/info/info.component.ts index 1711d5b94..739e7bf8a 100644 --- a/projects/social_platform/src/app/office/projects/detail/info/info.component.ts +++ b/projects/social_platform/src/app/office/projects/detail/info/info.component.ts @@ -133,7 +133,8 @@ export class ProjectInfoComponent implements OnInit, AfterViewInit, OnDestroy { this.project?.actuality, "", "", - ] + ], + ["string", "string", "string", "array", "array"] )!; } diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.html b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.html index 907301d65..34ef8e461 100644 --- a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.html +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.html @@ -12,6 +12,34 @@
    + @if (type === 'string') {

    {{ about }}

    + } @else { +
    +
      + @for (aboutItem of about; track $index) { +
    • +

      {{ aboutItem?.name }}

      +
      + {{ aboutItem?.category?.name?.includes("Soft skills") ? "soft" : "hard" }} + {{ aboutItem?.category?.name }} +
      +
    • + } +
    +
    + }
    diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss index be8c59cd4..8bcedd86f 100644 --- a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss @@ -66,4 +66,32 @@ &__text { color: var(--black); } + + &__skill { + display: flex; + flex-direction: column; + gap: 8px; + + + &--block { + display: grid; + grid-template-columns: 2fr 2fr; + grid-gap: 20px; + } + } +} + +.skills { + &__info { + padding: 20px; + border: 0.5px solid var(--medium-grey-for-outline); + border-radius: var(--rounded-xl); + } + + &__category { + app-tag { + display: inline-flex; + width: 70px; + } + } } diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.ts b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.ts index 89a0f2a48..927ae5166 100644 --- a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.ts +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.ts @@ -4,18 +4,20 @@ import { CommonModule } from "@angular/common"; import { Component, Input } from "@angular/core"; import { ModalComponent } from "@ui/components/modal/modal.component"; import { IconComponent } from "@uilib"; +import { TagComponent } from "@ui/components/tag/tag.component"; @Component({ selector: "app-project-direction-card", templateUrl: "./project-direction-card.component.html", styleUrl: "./project-direction-card.component.scss", - imports: [CommonModule, IconComponent, ModalComponent], + imports: [CommonModule, IconComponent, ModalComponent, TagComponent], standalone: true, }) export class ProjectDirectionCard { @Input() direction!: string; @Input() icon!: string; - @Input() about!: string; + @Input() about!: string | any[]; + @Input() type!: string; isShowModal = false; } diff --git a/projects/social_platform/src/app/office/projects/edit/edit.component.scss b/projects/social_platform/src/app/office/projects/edit/edit.component.scss index 0a887c056..6f31674ad 100644 --- a/projects/social_platform/src/app/office/projects/edit/edit.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/edit.component.scss @@ -11,7 +11,7 @@ &__top { position: sticky; - top: 10%; + top: -50%; left: 6%; z-index: 100; display: flex; 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 0f4f7a6ea..c4ac3e2de 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 @@ -23,7 +23,7 @@ import { ProjectService } from "@services/project.service"; import { ButtonComponent, IconComponent, SelectComponent } from "@ui/components"; import { ModalComponent } from "@ui/components/modal/modal.component"; import { ValidationService } from "projects/core"; -import { Subscription, distinctUntilChanged, forkJoin, map, switchMap } from "rxjs"; +import { Subscription, distinctUntilChanged, forkJoin, map, of, switchMap } from "rxjs"; import { CommonModule, AsyncPipe } from "@angular/common"; import { ProjectNavigationComponent } from "./shared/project-navigation/project-navigation.component"; import { EditStep, ProjectStepService } from "./services/project-step.service"; @@ -412,13 +412,9 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { this.projectService .updateProject(projectId, payload) .pipe( - switchMap(() => - forkJoin({ - goals: this.saveOrEditGoals(projectId), - partners: this.projectPartnerService.savePartners(projectId), - resources: this.saveOrEditResources(projectId), - }) - ) + switchMap(() => this.saveOrEditGoals(projectId)), + switchMap(() => this.savePartners(projectId)), + switchMap(() => this.saveOrEditResources(projectId)) ) .subscribe({ next: () => { @@ -451,16 +447,30 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { private saveOrEditGoals(projectId: number) { const goals = this.goals.value; - const hasExistingGoals = goals.some((g: Partner) => g.id); + const hasExistingGoals = goals.some((g: Goal) => g.id); return hasExistingGoals ? this.projectGoalService.editGoals(projectId) : this.projectGoalService.saveGoals(projectId); } + private savePartners(projectId: number) { + const partners = this.partners.value; + + if (!partners.length) { + return of([]); + } + + return this.projectPartnerService.savePartners(projectId); + } + private saveOrEditResources(projectId: number) { const resources = this.resources.value; - const hasExistingResources = resources.some((r: any) => r.id != null); + const hasExistingResources = resources.some((r: Resource) => r.id != null); + + if (!resources.length) { + return of([]); + } return hasExistingResources ? this.projectResourceService.editResources(projectId) 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 ff8e2da54..d500e168e 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 @@ -140,6 +140,7 @@ export class ProjectFormService { */ private populateAchievementsFormArray(achievements: any[]): void { const achievementsFormArray = this.projectForm.get("achievements") as FormArray; + const currentYear = new Date().getFullYear(); while (achievementsFormArray.length !== 0) { achievementsFormArray.removeAt(0); @@ -149,7 +150,15 @@ export class ProjectFormService { const achievementGroup = this.fb.group({ id: achievement.id ?? index, title: [achievement.title || "", Validators.required], - status: [achievement.status || "", Validators.required], + status: [ + achievement.status || "", + [ + Validators.required, + Validators.min(2000), + Validators.max(currentYear), + Validators.pattern(/^\d{4}$/), + ], + ], }); achievementsFormArray.push(achievementGroup); }); diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.html b/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.html index 5b21e0144..2cf466293 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.html +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.html @@ -37,8 +37,9 @@ [tooltipPosition]="'left'" [tooltipWidth]="300" [error]="achievementsDate | controlError" - type="date" + type="text" placeholder="2022" + mask="0000" formControlName="status" > @if (achievementsDate | controlError: "required") { diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.ts b/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.ts index 14c4e41c5..96b708afd 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.ts +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.ts @@ -102,11 +102,20 @@ export class ProjectAchievementStepComponent { * Добавление достижения */ addAchievement(id?: number, achievementsName?: string, achievementsDate?: string): void { + const currentYear = new Date().getFullYear(); this.achievements.push( this.fb.group({ id: [id], title: [achievementsName ?? "", [Validators.required]], - status: [achievementsDate ?? "", [Validators.required]], + status: [ + achievementsDate ?? "", + [ + Validators.required, + Validators.min(2000), + Validators.max(currentYear), + Validators.pattern(/^\d{4}$/), + ], + ], }) ); 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 da74a78c0..8377e1d02 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 @@ -263,7 +263,9 @@ class="project__options" [formGroup]="goalForm" [style.gap]="hasGoals || hasLinks ? '0px' : '35px'" - [style.margin-top]="hasGoals && hasLinks ? '20px' : hasGoals ? '20px' : '35px'" + [style.margin-top]=" + hasGoals && hasLinks ? '20px' : hasGoals ? '20px' : hasLinks ? '20px' : '0px' + " > @if (hasGoals) {
    -
    +
    контакты } - прикрепить резюме PROCOLLAB diff --git a/projects/social_platform/src/app/office/vacancies/detail/info/info.component.ts b/projects/social_platform/src/app/office/vacancies/detail/info/info.component.ts index c93b5be99..fdd1145ca 100644 --- a/projects/social_platform/src/app/office/vacancies/detail/info/info.component.ts +++ b/projects/social_platform/src/app/office/vacancies/detail/info/info.component.ts @@ -105,9 +105,7 @@ export class VacancyInfoComponent implements OnInit { constructor() { // Создание формы отклика с валидацией this.sendForm = this.fb.group({ - // Мотивационное письмо: обязательное поле, минимум 20 символов, максимум 2000 whyMe: ["", [Validators.required, Validators.minLength(20), Validators.maxLength(2000)]], - // Прикрепленный файл резюме: обязательное поле accompanyingFile: ["", Validators.required], }); } diff --git a/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.html b/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.html index c953cf986..179d8f431 100644 --- a/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.html +++ b/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.html @@ -60,7 +60,7 @@
      @for (option of filterWorkScheduleOptions; track $index) {
    • diff --git a/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.scss b/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.scss index 0d8c8d895..a9abbdb58 100644 --- a/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.scss +++ b/projects/social_platform/src/app/office/vacancies/shared/filter/vacancy-filter.component.scss @@ -92,24 +92,24 @@ .filter { display: flex; flex-direction: column; - gap: 15px; + gap: 12px; width: 100%; span { display: block; - margin-bottom: 6px; - color: var(--gray); + margin-bottom: 12px; + color: var(--black); } &__checkbox { display: flex; align-items: center; - margin-bottom: 6px; + margin-bottom: 12px; color: var(--grey-for-text); cursor: pointer; app-checkbox { - margin-right: 15px; + margin-right: 12px; } } @@ -130,8 +130,6 @@ app-input { input { padding: 12px; - - @include typography.body-12; } } } diff --git a/projects/social_platform/src/app/ui/components/input/input.component.html b/projects/social_platform/src/app/ui/components/input/input.component.html index b3ceab966..0edc178d4 100644 --- a/projects/social_platform/src/app/ui/components/input/input.component.html +++ b/projects/social_platform/src/app/ui/components/input/input.component.html @@ -19,9 +19,8 @@ (input)="onInput($event)" (keydown.enter)="onEnter($event)" (blur)="onBlur()" - (focus)="onFocus()" [placeholder]="placeholder" - [type]="currentType" + [type]="type" [mask]="mask" [name]="name" [dropSpecialCharacters]="true" diff --git a/projects/social_platform/src/app/ui/components/input/input.component.ts b/projects/social_platform/src/app/ui/components/input/input.component.ts index afa4b7086..001510f68 100644 --- a/projects/social_platform/src/app/ui/components/input/input.component.ts +++ b/projects/social_platform/src/app/ui/components/input/input.component.ts @@ -53,7 +53,7 @@ import { NgxMaskModule } from "ngx-mask"; standalone: true, imports: [CommonModule, NgxMaskModule, IconComponent, TooltipComponent], }) -export class InputComponent implements OnInit, OnChanges, ControlValueAccessor { +export class InputComponent implements ControlValueAccessor { constructor() {} /** Текст подсказки */ @@ -102,15 +102,6 @@ export class InputComponent implements OnInit, OnChanges, ControlValueAccessor { return this.value; } - /** Изначальный тип поля (для восстановления после blur) */ - private originalType: "text" | "password" | "email" | "tel" | "date" | "radio" = "text"; - - /** Текущий активный тип поля */ - currentType: "text" | "password" | "email" | "tel" | "date" | "radio" = "text"; - - /** Флаг для отслеживания фокуса на поле даты */ - private isDateFieldFocused = false; - /** Состояние видимости подсказки */ isTooltipVisible = false; @@ -123,16 +114,6 @@ export class InputComponent implements OnInit, OnChanges, ControlValueAccessor { /** Событие изменения состояния радио (для внешних обработчиков) */ @Output() change = new EventEmitter(); - ngOnInit(): void { - this.updateCurrentType(); - } - - ngOnChanges(changes: SimpleChanges): void { - if (changes["type"]) { - this.updateCurrentType(); - } - } - /** Обработчик для радио */ onRadioChange(event: Event): void { if (this.type === "radio") { @@ -145,18 +126,6 @@ export class InputComponent implements OnInit, OnChanges, ControlValueAccessor { } } - /** Обновляет currentType на основе входящего type */ - private updateCurrentType(): void { - this.originalType = this.type; - - // Для поля даты показываем text, пока не в фокусе - if (this.type === "date" && !this.isDateFieldFocused) { - this.currentType = "text"; - } else { - this.currentType = this.type; - } - } - /** Показать подсказку */ showTooltip(): void { this.isTooltipVisible = true; @@ -176,21 +145,9 @@ export class InputComponent implements OnInit, OnChanges, ControlValueAccessor { /** Обработчик потери фокуса */ onBlur(): void { - if (this.originalType === "date") { - this.isDateFieldFocused = false; - this.currentType = "text"; - } this.onTouch(); } - /** Обработчик при фокусе */ - onFocus(): void { - if (this.originalType === "date") { - this.isDateFieldFocused = true; - this.currentType = "date"; - } - } - /** Текущее значение поля */ value = ""; diff --git a/projects/social_platform/src/app/utils/helpers/directionItemBuilder.ts b/projects/social_platform/src/app/utils/helpers/directionItemBuilder.ts index df6d86569..e58dc86ae 100644 --- a/projects/social_platform/src/app/utils/helpers/directionItemBuilder.ts +++ b/projects/social_platform/src/app/utils/helpers/directionItemBuilder.ts @@ -4,13 +4,15 @@ export interface DirectionItem { direction: string; icon: string; about: string; + type: string; } export const directionItemBuilder = ( amount: number, directions: string[], icons: string[], - abouts: string[] + abouts: string[] | any[], + types: string[] ) => { if (amount <= 0) return; @@ -18,5 +20,6 @@ export const directionItemBuilder = ( direction: directions[i], icon: icons[i], about: abouts[i], + type: types[i], })); }; From bcc641f9376d2e3ef302b9a61805a3d94f2e5c58 Mon Sep 17 00:00:00 2001 From: Awakich Date: Tue, 21 Oct 2025 17:30:25 +0300 Subject: [PATCH 109/126] add styles for directions & chats --- .../src/app/office/profile/edit/edit.component.scss | 2 +- .../project-direction-card.component.scss | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.scss b/projects/social_platform/src/app/office/profile/edit/edit.component.scss index 4175516aa..5ad8de8ed 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.scss +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.scss @@ -179,8 +179,8 @@ &__action { &--icons { display: flex; - gap: 10px; flex-direction: column; + gap: 10px; align-items: center; } } diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss index 8bcedd86f..556d35323 100644 --- a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss @@ -67,12 +67,11 @@ color: var(--black); } - &__skill { + &__skill { display: flex; flex-direction: column; gap: 8px; - &--block { display: grid; grid-template-columns: 2fr 2fr; From 6508799b9abfcbcd48a8730e782278d0f1addaf6 Mon Sep 17 00:00:00 2001 From: Awakich Date: Fri, 24 Oct 2025 15:37:10 +0300 Subject: [PATCH 110/126] add achievements section in profile & fix goals logic in edit project --- projects/skills/src/app/app.routes.ts | 16 +- projects/skills/src/models/step.model.ts | 1 + .../src/app/auth/models/user.model.ts | 2 + .../src/app/auth/services/profile.service.ts | 4 + .../features/detail/detail.component.html | 38 +++ .../profile/detail/main/main.component.html | 1 + .../profile/detail/main/main.component.ts | 4 +- .../office/profile/edit/edit.component.html | 227 +++++++++++------- .../office/profile/edit/edit.component.scss | 9 +- .../app/office/profile/edit/edit.component.ts | 198 +++++++++++---- .../program/services/program.service.ts | 2 - .../project-direction-card.component.html | 4 +- .../project-direction-card.component.ts | 2 + .../edit/services/project-goals.service.ts | 69 ++---- .../services/project-resources.service.ts | 3 - .../project-achievement-step.component.scss | 3 + .../project-additional-step.component.scss | 3 + .../project-main-step.component.scss | 3 + ...ject-partner-resources-step.component.scss | 3 + .../project-team-step.component.scss | 3 + .../project-vacancy-step.component.scss | 3 + .../textarea/textarea.component.scss | 5 + 22 files changed, 395 insertions(+), 208 deletions(-) diff --git a/projects/skills/src/app/app.routes.ts b/projects/skills/src/app/app.routes.ts index b63e2ff38..b0eb79ec1 100644 --- a/projects/skills/src/app/app.routes.ts +++ b/projects/skills/src/app/app.routes.ts @@ -29,18 +29,18 @@ export const routes: Routes = [ path: "profile", loadChildren: () => import("./profile/profile.routes").then(c => c.PROFILE_ROUTES), }, - // { - // path: "skills", - // loadChildren: () => import("./skills/skills.routes").then(c => c.SKILLS_ROUTES), - // }, + { + path: "skills", + loadChildren: () => import("./skills/skills.routes").then(c => c.SKILLS_ROUTES), + }, // { // path: "rating", // loadChildren: () => import("./rating/rating.routes").then(c => c.RATING_ROUTES), // }, - // { - // path: "task", - // loadChildren: () => import("./task/task.routes").then(c => c.TASK_ROUTES), - // }, + { + path: "task", + loadChildren: () => import("./task/task.routes").then(c => c.TASK_ROUTES), + }, // { // path: "trackBuss", // loadChildren: () => diff --git a/projects/skills/src/models/step.model.ts b/projects/skills/src/models/step.model.ts index 4802ab3f4..bd61e7535 100644 --- a/projects/skills/src/models/step.model.ts +++ b/projects/skills/src/models/step.model.ts @@ -30,6 +30,7 @@ export interface InfoSlide extends BaseStep { description: string; // Дополнительное описание или контекст files: string[]; // Массив URL файлов для отображения (изображения, документы) popups: Popup[]; // Всплывающие окна для отображения после просмотра + videoUrl?: string; // Ссылка для видео } /** diff --git a/projects/social_platform/src/app/auth/models/user.model.ts b/projects/social_platform/src/app/auth/models/user.model.ts index 59d404740..2ead5760a 100644 --- a/projects/social_platform/src/app/auth/models/user.model.ts +++ b/projects/social_platform/src/app/auth/models/user.model.ts @@ -24,6 +24,8 @@ export class Achievement { id!: number; title!: string; status!: string; + year!: number; + files!: string[]; } export class Education { diff --git a/projects/social_platform/src/app/auth/services/profile.service.ts b/projects/social_platform/src/app/auth/services/profile.service.ts index 167276827..984a0e909 100644 --- a/projects/social_platform/src/app/auth/services/profile.service.ts +++ b/projects/social_platform/src/app/auth/services/profile.service.ts @@ -29,6 +29,10 @@ export class ProfileService { constructor(private apiService: ApiService) {} + getAchievements(): Observable { + return this.apiService.get(`${this.AUTH_USERS_URL}/achievements/`); + } + addAchievement(achievement: Omit): Observable { return this.apiService .post(`${this.AUTH_USERS_URL}/achievements/`, achievement) 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 0db59c92c..501df92db 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 @@ -81,6 +81,8 @@ } } @else if (isUserMember) { @@ -339,6 +341,42 @@ } } + +
      + + idea +

      редактирование недоступно

      + +

      + Этот проект уже отправлен на конкурс.
      Изменения будут доступны только после + окончания конкурса. +

      + + хорошо +
      +
      +
      diff --git a/projects/social_platform/src/app/office/profile/detail/main/main.component.html b/projects/social_platform/src/app/office/profile/detail/main/main.component.html index 764ff4060..7bc19a659 100644 --- a/projects/social_platform/src/app/office/profile/detail/main/main.component.html +++ b/projects/social_platform/src/app/office/profile/detail/main/main.component.html @@ -114,6 +114,7 @@

      обо мне

      [icon]="directionItem.icon" [about]="directionItem.about" [type]="directionItem.type" + [profileInfoType]="directionItem.direction === 'навыки' ? 'skills' : 'achievements'" > }
      diff --git a/projects/social_platform/src/app/office/profile/detail/main/main.component.ts b/projects/social_platform/src/app/office/profile/detail/main/main.component.ts index e67f57bb0..73e137696 100644 --- a/projects/social_platform/src/app/office/profile/detail/main/main.component.ts +++ b/projects/social_platform/src/app/office/profile/detail/main/main.component.ts @@ -111,8 +111,8 @@ export class ProfileMainComponent implements OnInit, AfterViewInit, OnDestroy { 2, ["навыки", "достижения"], ["squiz", "medal"], - [user.skills, ""], - ["array", "string"] + [user.skills, user.achievements], + ["array", "array"] )!; } this.user = user as User; diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.html b/projects/social_platform/src/app/office/profile/edit/edit.component.html index bd4547840..1a39eb9ec 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.html +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.html @@ -16,7 +16,8 @@

      редактирование профиля

      editLanguageClick || isEducationDirty || isWorkDirty || - isLanguageDirty + isLanguageDirty || + isAchievementsDirty " [loader]="profileFormSubmitting" [ngStyle]="{ @@ -26,7 +27,8 @@

      редактирование профиля

      editLanguageClick || isEducationDirty || isWorkDirty || - isLanguageDirty + isLanguageDirty || + isAchievementsDirty ? '0.6' : '1', cursor: @@ -35,7 +37,8 @@

      редактирование профиля

      editLanguageClick || isEducationDirty || isWorkDirty || - isLanguageDirty + isLanguageDirty || + isAchievementsDirty ? 'not-allowed' : 'pointer' }" @@ -652,7 +655,7 @@

      редактирование профиля

      @if(workItems().length || workExperience.length){ @for (workItem of workExperience.value; track $index) {
      -

      +

      {{ workItem.organizationName }}

      @@ -698,103 +701,144 @@

      редактирование профиля

      } @if(editingStep === 'achievements'){
      - -
        - @for (control of achievements.controls; track control.value.id; let i = $index) { -
      • -
        -
        - @if (achievements.at(i)?.get("title"); as title) { -
        - - - @if (title | controlError: "required") { -
        - {{ errorMessage.VALIDATION_REQUIRED }} -
        - } -
        - } @if (achievements.at(i)?.get("status"); as status) { -
        - - - @if (status | controlError: "required") { -
        - {{ errorMessage.VALIDATION_REQUIRED }} -
        - } -
        - } @if (achievements.at(i)?.get("year"); as year) { -
        - - - @if (year | controlError: "required") { -
        - {{ errorMessage.VALIDATION_REQUIRED }} -
        - } -
        - } @if (achievements.at(i)?.get("files"); as files) { -
        - - - -

        - презентация формата .PDF -
        - или .PPTX весом до 50МБ -

        - @if (files | controlError: "required") { -

        загрузите файл

        - } -
        -
        -
        - } + @if (showAchievementsFields) { +
        + @if (profileForm.get("title"); as title) { +
        + + + @if (title | controlError: "required") { +
        + {{ errorMessage.VALIDATION_REQUIRED }} +
        + } +
        + } +
        - - Удалить - - -
        -
        -
      • +
        + @if (profileForm.get("year"); as year) { +
        + + + @if (year | controlError: "required") { +
        + {{ errorMessage.VALIDATION_REQUIRED }} +
        } -
      -
      + + } +
      + +
      + @if (profileForm.get("status"); as status) { +
      + + + @if (status | controlError: "required") { +
      + {{ errorMessage.VALIDATION_REQUIRED }} +
      + } +
      + } +
      + +
      + @if (profileForm.get("files"); as files) { +
      + + + +

      + файл или изображение
      + с сертификатом подтверждающим
      + достижение весом до 50МБ +

      + @if (files | controlError: "required") { +

      загрузите файл

      + } +
      +
      +
      + } +
      + } - Добавить достижение + добавить достижение
      + +
      + @if(achievementItems().length || achievements.length){ @for (achievementItem of + achievements.value; track $index) { +
      +
      +
      +

      + {{ achievementItem.title }} +

      + +

      + {{ achievementItem.year }} +

      + +

      + {{ achievementItem.status }} +

      +
      + +
      +
      + +
      + +
      + +
      +
      +
      +
      + } } +
      } @if (editingStep === 'skills') {
      @@ -882,7 +926,8 @@

      редактирование профиля

      appearance="outline" [disabled]=" (showLanguageFields && !isLanguageDirty) || - languageItems().length === 4 || userLanguages.length === 4 + languageItems().length === 4 || + userLanguages.length === 4 " [ngStyle]="{ opacity: showLanguageFields && !isLanguageDirty ? '0.6' : '1', diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.scss b/projects/social_platform/src/app/office/profile/edit/edit.component.scss index 5ad8de8ed..60fe822a6 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.scss +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.scss @@ -225,10 +225,6 @@ display: flex; flex-direction: column; gap: 20px; - - :first-child & :not(i) { - margin-top: 10px; - } } .profile__language--list { @@ -288,7 +284,6 @@ } &__text { - margin-top: 10px; color: var(--grey-for-text); } } @@ -372,3 +367,7 @@ text-align: center; } } + +.error { + color: var(--red) !important; +} diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.ts b/projects/social_platform/src/app/office/profile/edit/edit.component.ts index a2466dd51..d6e5c7710 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.ts +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.ts @@ -20,7 +20,7 @@ import { import { ErrorMessage } from "@error/models/error-message"; import { ButtonComponent, IconComponent, InputComponent, SelectComponent } from "@ui/components"; import { ControlErrorPipe, ValidationService } from "projects/core"; -import { concatMap, first, map, noop, Observable, skip, Subscription } from "rxjs"; +import { concatMap, first, map, noop, Observable, skip, Subscription, switchMap } from "rxjs"; import { ActivatedRoute, Router, RouterModule } from "@angular/router"; import * as dayjs from "dayjs"; import * as cpf from "dayjs/plugin/customParseFormat"; @@ -43,10 +43,11 @@ import { educationUserLevel, educationUserType } from "projects/core/src/consts/ import { languageLevelsList, languageNamesList } from "projects/core/src/consts/list-language"; import { transformYearStringToNumber } from "@utils/transformYear"; import { yearRangeValidators } from "@utils/yearRangeValidators"; -import { User } from "@auth/models/user.model"; +import { Achievement, User } from "@auth/models/user.model"; import { SwitchComponent } from "@ui/components/switch/switch.component"; import { generateOptionsList } from "@utils/generate-options-list"; import { UploadFileComponent } from "@ui/components/upload-file/upload-file.component"; +import { ProfileService } from "@auth/services/profile.service"; dayjs.extend(cpf); @@ -103,6 +104,7 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { constructor( private readonly cdref: ChangeDetectorRef, public readonly authService: AuthService, + private readonly profileService: ProfileService, private readonly fb: FormBuilder, private readonly validationService: ValidationService, private readonly specsService: SpecializationsService, @@ -136,10 +138,17 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { language: [null], languageLevel: [null], + // achievements + title: [null], + status: [null], + year: [null], + files: [""], + education: this.fb.array([]), workExperience: this.fb.array([]), userLanguages: this.fb.array([]), links: this.fb.array([]), + achievements: this.fb.array([]), // work organization: ["", Validators.max(100)], @@ -151,7 +160,6 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { // skills speciality: ["", [Validators.required]], skills: [[]], - achievements: this.fb.array([]), avatar: [""], aboutMe: [""], typeSpecific: this.fb.group({}), @@ -182,20 +190,20 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { userAvatar$ && this.subscription$.push(userAvatar$); - const isMospolytechStudentSub$ = this.profileForm - .get("isMospolytechStudent") - ?.valueChanges.subscribe(isStudent => { - const studyGroup = this.profileForm.get("studyGroup"); - if (isStudent) { - studyGroup?.setValidators([Validators.required]); - } else { - studyGroup?.clearValidators(); - } + // const isMospolytechStudentSub$ = this.profileForm + // .get("isMospolytechStudent") + // ?.valueChanges.subscribe(isStudent => { + // const studyGroup = this.profileForm.get("studyGroup"); + // if (isStudent) { + // studyGroup?.setValidators([Validators.required]); + // } else { + // studyGroup?.clearValidators(); + // } - studyGroup?.updateValueAndValidity(); - }); + // studyGroup?.updateValueAndValidity(); + // }); - isMospolytechStudentSub$ && this.subscription$.push(isMospolytechStudentSub$); + // isMospolytechStudentSub$ && this.subscription$.push(isMospolytechStudentSub$); this.editingStep = this.route.snapshot.queryParams["editingStep"]; } @@ -212,7 +220,6 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { firstName: profile.firstName ?? "", lastName: profile.lastName ?? "", email: profile.email ?? "", - status: profile.userType ?? "", userType: profile.userType ?? 1, birthday: profile.birthday ? dayjs(profile.birthday).format("DD.MM.YYYY") : "", city: profile.city ?? "", @@ -276,10 +283,20 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { this.cdref.detectChanges(); - profile.achievements.length && - profile.achievements?.forEach(achievement => - this.addAchievement(achievement.id, achievement.title, achievement.status) + this.achievements.clear(); + profile.achievements.forEach(achievement => { + this.achievements.push( + this.fb.group({ + title: achievement.title, + status: achievement.status, + year: achievement.year, + files: + Array.isArray(achievement.files) && achievement.files.length > 0 + ? achievement.files[0] + : "", + }) ); + }); profile.links.length && profile.links.forEach(l => this.addLink(l)); @@ -337,6 +354,8 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { languageItems = signal([]); + achievementItems = signal([]); + isModalErrorSkillsChoose = signal(false); isModalErrorSkillChooseText = signal(""); @@ -347,10 +366,12 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { editEducationClick = false; editWorkClick = false; editLanguageClick = false; + editAchievementsClick = false; showEducationFields = false; showWorkFields = false; showLanguageFields = false; + showAchievementsFields = false; selectedEntryYearEducationId = signal(undefined); selectedComplitionYearEducationId = signal(undefined); @@ -360,6 +381,8 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { selectedEntryYearWorkId = signal(undefined); selectedComplitionYearWorkId = signal(undefined); + selectedAchievementsYearId = signal(undefined); + selectedLanguageId = signal(undefined); selectedLanguageLevelId = signal(undefined); @@ -382,11 +405,6 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { | "settings"; } - isStudentMosPolytech(): void { - const ctl = this.profileForm.get("isMospolytechStudent"); - ctl?.setValue(!ctl.value); - } - readonly yearListEducation = generateOptionsList(55, "years"); readonly educationStatusList = educationUserType; @@ -397,6 +415,8 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { readonly languageLevelList = languageLevelsList; + readonly achievementsYearList = generateOptionsList(25, "years"); + get typeSpecific(): FormGroup { return this.profileForm.get("typeSpecific") as FormGroup; } @@ -471,6 +491,11 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { return ["language", "languageLevel"].some(name => f.get(name)?.dirty); } + get isAchievementsDirty(): boolean { + const f = this.profileForm; + return ["title", "status", "year", "files"].some(name => f.get(name)?.dirty); + } + errorMessage = ErrorMessage; roles: Observable = this.authService.changeableRoles.pipe( @@ -480,25 +505,101 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { profileFormSubmitting = false; profileForm: FormGroup; - addAchievement( - id?: number, - title?: string, - status?: string, - year?: number, - files?: string[] - ): void { - this.achievements.push( - this.fb.group({ - title: [title ?? "", [Validators.required]], - status: [status ?? "", [Validators.required]], - year: [year?.toString() ?? "", [Validators.required]], - files: [files ?? []], - id: [id], - }) + /** + * Добавление записи об достижении + * Валидирует форму и добавляет новую запись в массив достижений + */ + addAchievement(): void { + if (!this.showAchievementsFields) { + this.showAchievementsFields = true; + + this.profileForm.patchValue({ + title: "", + status: "", + year: null, + files: "", + }); + + return; + } + + ["title", "status", "year"].forEach(name => this.profileForm.get(name)?.clearValidators()); + ["title", "status", "year"].forEach(name => + this.profileForm.get(name)?.setValidators([Validators.required]) ); + ["title", "status", "year"].forEach(name => + this.profileForm.get(name)?.updateValueAndValidity() + ); + ["title", "status", "year"].forEach(name => this.profileForm.get(name)?.markAsTouched()); + + const achievementsYear = + typeof this.profileForm.get("year")?.value === "string" + ? +this.profileForm.get("year")?.value.slice(0, 5) + : this.profileForm.get("year")?.value; + + const achievementsItem = this.fb.group({ + title: this.profileForm.get("title")?.value, + status: this.profileForm.get("status")?.value, + year: achievementsYear, + files: this.profileForm.get("files")?.value || "", + }); + + if (this.editIndex() !== null) { + this.achievementItems.update(items => { + const updatedItems = [...items]; + updatedItems[this.editIndex()!] = achievementsItem.value; + + this.achievements.at(this.editIndex()!).patchValue(achievementsItem.value); + return updatedItems; + }); + this.editIndex.set(null); + } else { + this.achievementItems.update(items => [...items, achievementsItem.value]); + this.achievements.push(achievementsItem); + } + ["title", "status", "year", "files"].forEach(name => { + this.profileForm.get(name)?.reset(); + this.profileForm.get(name)?.setValue(""); + this.profileForm.get(name)?.clearValidators(); + this.profileForm.get(name)?.markAsPristine(); + this.profileForm.get(name)?.markAsUntouched(); + this.profileForm.get(name)?.updateValueAndValidity(); + }); + + this.showAchievementsFields = false; + this.editAchievementsClick = false; } + /** + * Редактирование записи об достижений + * @param index - индекс записи в массиве достижений + */ + editAchievements(index: number) { + this.editAchievementsClick = true; + this.showAchievementsFields = true; + const achievementItem = this.achievements.value[index]; + + this.achievementsYearList.forEach(achievementYear => { + if (transformYearStringToNumber(achievementYear.value as string) === achievementItem.year) { + this.selectedAchievementsYearId.set(achievementYear.id); + } + }); + + this.profileForm.patchValue({ + title: achievementItem.title, + status: achievementItem.status, + year: achievementItem.year, + files: achievementItem.files, + }); + this.editIndex.set(index); + } + + /** + * Удаление записи об достижении + * @param i - индекс записи для удаления + */ removeAchievement(i: number): void { + this.achievementItems.update(items => items.filter((_, index) => index !== i)); this.achievements.removeAt(i); } @@ -893,8 +994,16 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { this.profileFormSubmitting = true; + const achievements = this.achievements.value.map((achievement: any) => ({ + title: achievement.title, + status: achievement.status, + year: achievement.year, + files: achievement.files && achievement.files.trim() !== "" ? [achievement.files] : [], + })); + const newProfile = { ...this.profileForm.value, + achievements, [this.userTypeMap[this.profileForm.value.userType]]: this.typeSpecific.value, typeSpecific: undefined, birthday: this.profileForm.value.birthday @@ -907,15 +1016,18 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { : this.profileForm.value.phoneNumber, }; + console.log(newProfile); + this.authService .saveProfile(newProfile) .pipe(concatMap(() => this.authService.getProfile())) .subscribe({ - next: () => { + next: res => { this.profileFormSubmitting = false; - this.router - .navigateByUrl(`/office/profile/${this.profileId}`) - .then(() => console.debug("Router Changed form ProfileEditComponent")); + console.log(res); + // this.router + // .navigateByUrl(`/office/profile/${this.profileId}`) + // .then(() => console.debug("Router Changed form ProfileEditComponent")); }, error: error => { this.profileFormSubmitting = 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 48e7c53f3..ce11970d7 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 @@ -45,9 +45,7 @@ import { PartnerProgramFields } from "@office/models/partner-program-fields.mode }) export class ProgramService { private readonly PROGRAMS_URL = "/programs"; - private readonly PROJECTS_URL = "/projects"; private readonly AUTH_PUBLIC_USERS_URL = "/auth/public-users"; - private readonly AUTH_USERS_CURRENT_URL = "/auth/users/current"; constructor(private readonly apiService: ApiService) {} diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.html b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.html index 34ef8e461..514824907 100644 --- a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.html +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.html @@ -14,7 +14,7 @@ @if (type === 'string') {

      {{ about }}

      - } @else { + } @else { @if (profileInfoType === "skills") {
        @for (aboutItem of about; track $index) { @@ -40,6 +40,6 @@ }
      - } + } @else { } }
      diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.ts b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.ts index 927ae5166..1a494f1bc 100644 --- a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.ts +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.ts @@ -19,5 +19,7 @@ export class ProjectDirectionCard { @Input() about!: string | any[]; @Input() type!: string; + @Input() profileInfoType?: "skills" | "achievements" = "skills"; + isShowModal = false; } diff --git a/projects/social_platform/src/app/office/projects/edit/services/project-goals.service.ts b/projects/social_platform/src/app/office/projects/edit/services/project-goals.service.ts index 5250b9636..9aff1cc92 100644 --- a/projects/social_platform/src/app/office/projects/edit/services/project-goals.service.ts +++ b/projects/social_platform/src/app/office/projects/edit/services/project-goals.service.ts @@ -152,6 +152,7 @@ export class ProjectGoalService { } const goalItem = this.fb.group({ + id: [null], title: [name.trim(), Validators.required], completionDate: [date.trim(), Validators.required], responsible: [leader, Validators.required], @@ -160,17 +161,14 @@ export class ProjectGoalService { const editIdx = this.projectFormService.editIndex(); if (editIdx !== null) { - this.goalItems.update(items => { - const updated = [...items]; - updated[editIdx] = goalItem.value; - return updated; - }); goalFormArray.at(editIdx).patchValue(goalItem.value); this.projectFormService.editIndex.set(null); } else { this.goalItems.update(items => [...items, goalItem.value]); goalFormArray.push(goalItem); } + + this.syncGoalItems(goalFormArray); } /** @@ -295,35 +293,25 @@ export class ProjectGoalService { * После ответов присваивает полученные id в соответствующие FormGroup. * Возвращает Observable массива результатов (в порядке отправки). */ - public saveGoals(projectId: number) { - const goals = this.getGoalsData(); - - return this.projectService.addGoals(projectId, goals).pipe( + public saveGoals(projectId: number, newGoals: Goal[]) { + return this.projectService.addGoals(projectId, newGoals).pipe( tap(results => { - if (Array.isArray(results)) { - results.forEach((createdGoal: any, idx: number) => { - if (createdGoal && createdGoal.id !== undefined && createdGoal.id !== null) { - const formGroup = this.goals.at(idx); - if (formGroup) { - formGroup.get("id")?.setValue(createdGoal.id); - } - } - }); - } - - this.syncGoalItems(this.goals); + results.forEach((createdGoal: any, idx: number) => { + const formGroup = this.goals.at(idx); + if (formGroup && createdGoal?.id != null) { + formGroup.patchValue({ id: createdGoal.id }); + } + }); }), catchError(err => { console.error("Error saving goals:", err); - return of({ __error: true, err, original: goals }); + return of({ __error: true, err, original: newGoals }); }) ); } - public editGoals(projectId: number) { - const goals = this.getGoalsData(); - - const requests = goals.map(item => { + public editGoals(projectId: number, existingGoals: Goal[]) { + const requests = existingGoals.map((item, idx) => { const payload: GoalPostForm = { id: item.id, title: item.title, @@ -333,35 +321,12 @@ export class ProjectGoalService { }; return this.projectService.editGoal(projectId, item.id, payload).pipe( - map((res: any) => ({ res, idx: item.idx })), - catchError(err => of({ __error: true, err, original: item.g, idx: item.idx })) + map(res => ({ res, idx })), + catchError(err => of({ __error: true, err, original: item, idx })) ); }); - return forkJoin(requests).pipe( - tap(results => { - results.forEach((r: any) => { - if (r && r.__error) { - console.error("Failed to post goal", r.err, "original:", r.original); - return; - } - - const created = r.res; - const idx = r.idx; - - if (created && created.id !== undefined && created.id !== null) { - const formGroup = this.goals.at(idx); - if (formGroup) { - formGroup.get("id")?.setValue(created.id); - } - } else { - console.warn("postGoal response has no id field:", r.res); - } - }); - - this.syncGoalItems(this.goals); - }) - ); + return forkJoin(requests); } /** diff --git a/projects/social_platform/src/app/office/projects/edit/services/project-resources.service.ts b/projects/social_platform/src/app/office/projects/edit/services/project-resources.service.ts index 85da03b03..15b661535 100644 --- a/projects/social_platform/src/app/office/projects/edit/services/project-resources.service.ts +++ b/projects/social_platform/src/app/office/projects/edit/services/project-resources.service.ts @@ -202,7 +202,6 @@ export class ProjectResourceService { }; return this.projectService.addResource(projectId, payload).pipe( - tap(() => console.log(payload)), map((res: any) => ({ res, idx: resource.idx })), catchError(err => of({ __error: true, err, original: resource })) ); @@ -224,8 +223,6 @@ export class ProjectResourceService { if (formGroup) { formGroup.get("id")?.setValue(created.id); } - } else { - console.warn("addResource response has no id field:", r.res); } }); diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.scss index a2524fef8..31044e5ea 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.scss @@ -74,5 +74,8 @@ } .error { + i { + color: var(--red) !important; + } color: var(--red) !important; } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.scss index 6f8ce9eb4..4da7d2bb8 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.scss @@ -162,5 +162,8 @@ } .error { + i { + color: var(--red) !important; + } color: var(--red) !important; } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.scss index 28f4a009c..5c1bde5c0 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.scss @@ -135,6 +135,9 @@ } .error { + i { + color: var(--red) !important; + } color: var(--red) !important; } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.scss index a210e52ec..567a6017e 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.scss @@ -59,5 +59,8 @@ } .error { + i { + color: var(--red) !important; + } color: var(--red) !important; } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss index d06b8e25f..8e9478063 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss @@ -129,6 +129,9 @@ } .error { + i { + color: var(--red) !important; + } color: var(--red) !important; } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.scss index d72519b84..3686a0c6d 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.scss @@ -94,6 +94,9 @@ } .error { + i { + color: var(--red) !important; + } color: var(--red) !important; } diff --git a/projects/social_platform/src/app/ui/components/textarea/textarea.component.scss b/projects/social_platform/src/app/ui/components/textarea/textarea.component.scss index ba701cd42..7d3edc1b5 100644 --- a/projects/social_platform/src/app/ui/components/textarea/textarea.component.scss +++ b/projects/social_platform/src/app/ui/components/textarea/textarea.component.scss @@ -51,6 +51,11 @@ align-items: center; justify-content: center; transform: translateY(-50%); + color: var(--red) !important; + + i { + color: var(--red) !important; + } } &__tooltip-wrapper { From 1f3ab38d78f498401ed54c73a217f747067be978 Mon Sep 17 00:00:00 2001 From: Awakich Date: Fri, 24 Oct 2025 15:40:10 +0300 Subject: [PATCH 111/126] add styles for erro icons & fix goal logic to save --- .../office/projects/edit/edit.component.html | 12 ++++-- .../office/projects/edit/edit.component.ts | 43 ++++++++++++++++--- .../project-achievement-step.component.scss | 1 + .../project-additional-step.component.scss | 1 + .../project-main-step.component.scss | 1 + ...ject-partner-resources-step.component.scss | 1 + .../project-team-step.component.scss | 1 + .../project-vacancy-step.component.scss | 1 + .../textarea/textarea.component.scss | 2 +- 9 files changed, 52 insertions(+), 11 deletions(-) diff --git a/projects/social_platform/src/app/office/projects/edit/edit.component.html b/projects/social_platform/src/app/office/projects/edit/edit.component.html index f564d9ba1..7fe9a7a38 100644 --- a/projects/social_platform/src/app/office/projects/edit/edit.component.html +++ b/projects/social_platform/src/app/office/projects/edit/edit.component.html @@ -86,14 +86,20 @@

      редактировать проект

      " >
      -

      📢 Внимание!

      +

      📢 внимание!

      - Для публикации проекта, нужно заполнить все обязательные поля (они будут + для публикации проекта, нужно заполнить все обязательные поля (они будут подсвечены красным). Если вы пока не знаете что написать, можно сохранить черновик проекта и заполнить поля позже :)

      - Понятно + понятно
      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 c4ac3e2de..f6e1ac806 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 @@ -23,7 +23,16 @@ import { ProjectService } from "@services/project.service"; import { ButtonComponent, IconComponent, SelectComponent } from "@ui/components"; import { ModalComponent } from "@ui/components/modal/modal.component"; import { ValidationService } from "projects/core"; -import { Subscription, distinctUntilChanged, forkJoin, map, of, switchMap } from "rxjs"; +import { + Observable, + Subscription, + distinctUntilChanged, + forkJoin, + map, + of, + switchMap, + tap, +} from "rxjs"; import { CommonModule, AsyncPipe } from "@angular/common"; import { ProjectNavigationComponent } from "./shared/project-navigation/project-navigation.component"; import { EditStep, ProjectStepService } from "./services/project-step.service"; @@ -446,12 +455,30 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { } private saveOrEditGoals(projectId: number) { - const goals = this.goals.value; - const hasExistingGoals = goals.some((g: Goal) => g.id); + const goals = this.goals.value as Goal[]; + + const newGoals = goals.filter(g => !g.id); + const existingGoals = goals.filter(g => g.id); + + const requests: Observable[] = []; + + if (newGoals.length > 0) { + requests.push(this.projectGoalService.saveGoals(projectId, newGoals)); + } + + if (existingGoals.length > 0) { + requests.push(this.projectGoalService.editGoals(projectId, existingGoals)); + } + + if (requests.length === 0) { + return of(null); + } - return hasExistingGoals - ? this.projectGoalService.editGoals(projectId) - : this.projectGoalService.saveGoals(projectId); + return forkJoin(requests).pipe( + tap(() => { + this.projectGoalService.syncGoalItems(this.projectGoalService.goals); + }) + ); } private savePartners(projectId: number) { @@ -491,10 +518,12 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { private validateAdditionalFields(): boolean { const partnerProgramFields = this.projectAdditionalService.getPartnerProgramFields(); + // Если нет дополнительных полей - пропускаем валидацию if (!partnerProgramFields?.length) { return false; } + // Проверяем только обязательные поля const hasInvalid = this.projectAdditionalService.validateRequiredFields(); if (hasInvalid) { @@ -502,7 +531,7 @@ export class ProjectEditComponent implements OnInit, AfterViewInit, OnDestroy { return true; } - // Подготавливаем поля для отправки + // Подготавливаем поля для отправки (убираем валидаторы с заполненных полей) this.projectAdditionalService.prepareFieldsForSubmit(); return false; } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.scss index 31044e5ea..29e19d720 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-achievement-step/project-achievement-step.component.scss @@ -77,5 +77,6 @@ i { color: var(--red) !important; } + color: var(--red) !important; } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.scss index 4da7d2bb8..6092cd72f 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-additional-step/project-additional-step.component.scss @@ -165,5 +165,6 @@ i { color: var(--red) !important; } + color: var(--red) !important; } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.scss index 5c1bde5c0..94dc20ce5 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.scss @@ -138,6 +138,7 @@ i { color: var(--red) !important; } + color: var(--red) !important; } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.scss index 567a6017e..0b2a745fc 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.scss @@ -62,5 +62,6 @@ i { color: var(--red) !important; } + color: var(--red) !important; } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss index 8e9478063..a3e75f622 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.scss @@ -132,6 +132,7 @@ i { color: var(--red) !important; } + color: var(--red) !important; } diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.scss b/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.scss index 3686a0c6d..d74b3feea 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.scss +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.scss @@ -97,6 +97,7 @@ i { color: var(--red) !important; } + color: var(--red) !important; } diff --git a/projects/social_platform/src/app/ui/components/textarea/textarea.component.scss b/projects/social_platform/src/app/ui/components/textarea/textarea.component.scss index 7d3edc1b5..f9b888665 100644 --- a/projects/social_platform/src/app/ui/components/textarea/textarea.component.scss +++ b/projects/social_platform/src/app/ui/components/textarea/textarea.component.scss @@ -50,8 +50,8 @@ display: flex; align-items: center; justify-content: center; - transform: translateY(-50%); color: var(--red) !important; + transform: translateY(-50%); i { color: var(--red) !important; From 1a5b3477331e0364db3478e3ecfb5da38254701a Mon Sep 17 00:00:00 2001 From: Awakich Date: Sat, 25 Oct 2025 15:20:39 +0300 Subject: [PATCH 112/126] fix naming in const variables & add approve-skill modal & info components for approve skill module --- projects/core/src/consts/README.md | 34 +++++ .../feed-filter.const.ts} | 2 +- .../rating-filter.const.ts} | 2 +- .../tags-filter.const.ts} | 2 +- .../work-experience-filter.const.ts} | 2 +- .../work-format-filter.const.ts} | 2 +- .../work-schedule-filter.const.ts} | 2 +- .../education-info-list.const.ts} | 0 .../language-info-list.const.ts} | 0 .../ldirection-project-list.const.ts} | 2 +- .../mock-months-list.const.ts} | 0 .../resource-options-list.const.ts} | 0 .../roles-members-list.const.ts} | 0 .../track-project-list.const.ts} | 0 .../trajectory-more-list.const.ts} | 2 +- .../work-experience-list.const.ts} | 2 +- .../work-format-list.const.ts} | 2 +- .../work-schelude-list.const.ts} | 2 +- .../nav-profile-items.const.ts} | 2 +- .../nav-project-items.const.ts} | 2 +- .../profile-fields.const.ts} | 2 +- .../profile/home/profile-home.component.ts | 2 +- .../app/rating/general/general.component.ts | 2 +- .../shared/trajectory/trajectory.component.ts | 2 +- .../approve-skill.component.html | 31 +++++ .../approve-skill.component.scss | 20 +++ .../approve-skill/approve-skill.component.ts | 128 ++++++++++++++++++ .../features/detail/detail.component.html | 60 +------- .../features/detail/detail.component.scss | 30 ---- .../features/detail/detail.component.ts | 74 +--------- .../invite-card/invite-card.component.ts | 2 +- .../src/app/office/feed/feed.component.scss | 1 + .../feed/filter/feed-filter.component.html | 2 +- .../feed/filter/feed-filter.component.ts | 4 +- .../stage-zero/stage-zero.component.ts | 10 +- .../office/profile/edit/edit.component.html | 2 +- .../app/office/profile/edit/edit.component.ts | 27 ++-- .../program/detail/list/list.component.ts | 4 +- .../edit/services/project-vacancy.service.ts | 30 ++-- .../project-main-step.component.ts | 4 +- .../project-navigation.component.html | 2 +- .../project-navigation.component.ts | 4 +- ...roject-partner-resources-step.component.ts | 2 +- .../project-team-step.component.ts | 2 +- .../project-vacancy-step.component.ts | 6 +- .../projects-filter.component.ts | 24 +--- .../approve-skill-people.component.html | 15 ++ .../approve-skill-people.component.scss | 14 ++ .../approve-skill-people.component.ts | 18 +++ .../filter/vacancy-filter.component.html | 6 +- .../shared/filter/vacancy-filter.component.ts | 16 +-- .../src/app/utils/calculateProgress.ts | 8 +- 52 files changed, 361 insertions(+), 253 deletions(-) create mode 100644 projects/core/src/consts/README.md rename projects/core/src/consts/{feed-filter-list.ts => filters/feed-filter.const.ts} (94%) rename projects/core/src/consts/{rating-filters.ts => filters/rating-filter.const.ts} (89%) rename projects/core/src/consts/{filter-tags.ts => filters/tags-filter.const.ts} (82%) rename projects/core/src/consts/{filter-experience.ts => filters/work-experience-filter.const.ts} (88%) rename projects/core/src/consts/{filter-work-format.ts => filters/work-format-filter.const.ts} (86%) rename projects/core/src/consts/{filter-work-schedule.ts => filters/work-schedule-filter.const.ts} (90%) rename projects/core/src/consts/{list-education.ts => lists/education-info-list.const.ts} (100%) rename projects/core/src/consts/{list-language.ts => lists/language-info-list.const.ts} (100%) rename projects/core/src/consts/{list-direction-project.ts => lists/ldirection-project-list.const.ts} (90%) rename projects/core/src/consts/{list-mock-months.ts => lists/mock-months-list.const.ts} (100%) rename projects/core/src/consts/{resource-options-list.ts => lists/resource-options-list.const.ts} (100%) rename projects/core/src/consts/{list-roles-members.ts => lists/roles-members-list.const.ts} (100%) rename projects/core/src/consts/{list-track-project.ts => lists/track-project-list.const.ts} (100%) rename projects/core/src/consts/{trajectoryMore.ts => lists/trajectory-more-list.const.ts} (88%) rename projects/core/src/consts/{list-experience.ts => lists/work-experience-list.const.ts} (91%) rename projects/core/src/consts/{list-format.ts => lists/work-format-list.const.ts} (90%) rename projects/core/src/consts/{list-schelude.ts => lists/work-schelude-list.const.ts} (94%) rename projects/core/src/consts/{navProfileItems.ts => navigation/nav-profile-items.const.ts} (94%) rename projects/core/src/consts/{navProjectItems.ts => navigation/nav-project-items.const.ts} (96%) rename projects/core/src/consts/{fieldsProfile.ts => other/profile-fields.const.ts} (97%) create mode 100644 projects/social_platform/src/app/office/features/approve-skill/approve-skill.component.html create mode 100644 projects/social_platform/src/app/office/features/approve-skill/approve-skill.component.scss create mode 100644 projects/social_platform/src/app/office/features/approve-skill/approve-skill.component.ts create mode 100644 projects/social_platform/src/app/office/shared/approve-skill-people/approve-skill-people.component.html create mode 100644 projects/social_platform/src/app/office/shared/approve-skill-people/approve-skill-people.component.scss create mode 100644 projects/social_platform/src/app/office/shared/approve-skill-people/approve-skill-people.component.ts diff --git a/projects/core/src/consts/README.md b/projects/core/src/consts/README.md new file mode 100644 index 000000000..dfe7dcaaf --- /dev/null +++ b/projects/core/src/consts/README.md @@ -0,0 +1,34 @@ + + +# 🧱 Core / Const — правила нейминга + +## Общая идея + +Папка `core/const` хранит все константы проекта (навигация, списки для select, статусы, роли и т.д.). +Каждая константа должна быть названа единообразно и понятно по контексту. + +--- + +## 🧩 Имена файлов + +- Формат: `feature.const.ts` +- Название — в **kebab-case**. +- Примеры: + - `navigation.const.ts` + - `selects.const.ts` + - `permissions.const.ts` + +--- + +## 🧠 Имена переменных + +- Формат: **camelCase** +- Если переменная содержит список — использовать **множественное число** +- Имя отражает назначение +- Экспорт только через `export const` + +**Примеры:** + +```ts +export const navItems = [...] +``` diff --git a/projects/core/src/consts/feed-filter-list.ts b/projects/core/src/consts/filters/feed-filter.const.ts similarity index 94% rename from projects/core/src/consts/feed-filter-list.ts rename to projects/core/src/consts/filters/feed-filter.const.ts index cfbed7340..cfb85b78f 100644 --- a/projects/core/src/consts/feed-filter-list.ts +++ b/projects/core/src/consts/filters/feed-filter.const.ts @@ -1,6 +1,6 @@ /** @format */ -export const feedFilterlist = [ +export const feedFilter = [ { id: 1, name: "новости проектов", diff --git a/projects/core/src/consts/rating-filters.ts b/projects/core/src/consts/filters/rating-filter.const.ts similarity index 89% rename from projects/core/src/consts/rating-filters.ts rename to projects/core/src/consts/filters/rating-filter.const.ts index b411a41ca..56da6d564 100644 --- a/projects/core/src/consts/rating-filters.ts +++ b/projects/core/src/consts/filters/rating-filter.const.ts @@ -1,6 +1,6 @@ /** @format */ -export const ratingFiltersList = [ +export const ratingFilters = [ { label: "Месяц", id: 0, diff --git a/projects/core/src/consts/filter-tags.ts b/projects/core/src/consts/filters/tags-filter.const.ts similarity index 82% rename from projects/core/src/consts/filter-tags.ts rename to projects/core/src/consts/filters/tags-filter.const.ts index e08522394..7ff701ed6 100644 --- a/projects/core/src/consts/filter-tags.ts +++ b/projects/core/src/consts/filters/tags-filter.const.ts @@ -1,6 +1,6 @@ /** @format */ -export const filterTags = [ +export const tagsFilter = [ { id: 1, label: "Оцененные", value: true }, { id: 0, label: "Не оцененные", value: false }, ]; diff --git a/projects/core/src/consts/filter-experience.ts b/projects/core/src/consts/filters/work-experience-filter.const.ts similarity index 88% rename from projects/core/src/consts/filter-experience.ts rename to projects/core/src/consts/filters/work-experience-filter.const.ts index 281b6bf83..d0739c1c3 100644 --- a/projects/core/src/consts/filter-experience.ts +++ b/projects/core/src/consts/filters/work-experience-filter.const.ts @@ -1,6 +1,6 @@ /** @format */ -export const filterExperience = [ +export const workExperienceFilter = [ { label: "Без опыта", value: "no_experience" }, { label: "До 1 года", value: "up_to_a_year" }, { label: "От 1 года до 3 лет", value: "from_one_to_three_years" }, diff --git a/projects/core/src/consts/filter-work-format.ts b/projects/core/src/consts/filters/work-format-filter.const.ts similarity index 86% rename from projects/core/src/consts/filter-work-format.ts rename to projects/core/src/consts/filters/work-format-filter.const.ts index 6c0470259..d50b3c32e 100644 --- a/projects/core/src/consts/filter-work-format.ts +++ b/projects/core/src/consts/filters/work-format-filter.const.ts @@ -1,6 +1,6 @@ /** @format */ -export const filterWorkFormat = [ +export const workFormatFilter = [ { label: "Удаленная работа", value: "remote" }, { label: "Работа в офисе", value: "office" }, { label: "Смешанный формат", value: "hybrid" }, diff --git a/projects/core/src/consts/filter-work-schedule.ts b/projects/core/src/consts/filters/work-schedule-filter.const.ts similarity index 90% rename from projects/core/src/consts/filter-work-schedule.ts rename to projects/core/src/consts/filters/work-schedule-filter.const.ts index 950b9c844..f085cbedd 100644 --- a/projects/core/src/consts/filter-work-schedule.ts +++ b/projects/core/src/consts/filters/work-schedule-filter.const.ts @@ -1,6 +1,6 @@ /** @format */ -export const filterWorkSchedule = [ +export const workScheduleFilter = [ { label: "Полный рабочий день", value: "full_time" }, { label: "Сменный график", value: "shift_work" }, { label: "Гибкий график", value: "flexible_schedule" }, diff --git a/projects/core/src/consts/list-education.ts b/projects/core/src/consts/lists/education-info-list.const.ts similarity index 100% rename from projects/core/src/consts/list-education.ts rename to projects/core/src/consts/lists/education-info-list.const.ts diff --git a/projects/core/src/consts/list-language.ts b/projects/core/src/consts/lists/language-info-list.const.ts similarity index 100% rename from projects/core/src/consts/list-language.ts rename to projects/core/src/consts/lists/language-info-list.const.ts diff --git a/projects/core/src/consts/list-direction-project.ts b/projects/core/src/consts/lists/ldirection-project-list.const.ts similarity index 90% rename from projects/core/src/consts/list-direction-project.ts rename to projects/core/src/consts/lists/ldirection-project-list.const.ts index 6a18d18c3..c542dd096 100644 --- a/projects/core/src/consts/list-direction-project.ts +++ b/projects/core/src/consts/lists/ldirection-project-list.const.ts @@ -22,7 +22,7 @@ export const directionProjectList = [ }, { id: 3, - value: "им Био", // Возможно опечатка, должно быть "Хим Био" + value: "Хим Био", label: "Хим Био", }, { diff --git a/projects/core/src/consts/list-mock-months.ts b/projects/core/src/consts/lists/mock-months-list.const.ts similarity index 100% rename from projects/core/src/consts/list-mock-months.ts rename to projects/core/src/consts/lists/mock-months-list.const.ts diff --git a/projects/core/src/consts/resource-options-list.ts b/projects/core/src/consts/lists/resource-options-list.const.ts similarity index 100% rename from projects/core/src/consts/resource-options-list.ts rename to projects/core/src/consts/lists/resource-options-list.const.ts diff --git a/projects/core/src/consts/list-roles-members.ts b/projects/core/src/consts/lists/roles-members-list.const.ts similarity index 100% rename from projects/core/src/consts/list-roles-members.ts rename to projects/core/src/consts/lists/roles-members-list.const.ts diff --git a/projects/core/src/consts/list-track-project.ts b/projects/core/src/consts/lists/track-project-list.const.ts similarity index 100% rename from projects/core/src/consts/list-track-project.ts rename to projects/core/src/consts/lists/track-project-list.const.ts diff --git a/projects/core/src/consts/trajectoryMore.ts b/projects/core/src/consts/lists/trajectory-more-list.const.ts similarity index 88% rename from projects/core/src/consts/trajectoryMore.ts rename to projects/core/src/consts/lists/trajectory-more-list.const.ts index 63f222cfa..7a8861cbd 100644 --- a/projects/core/src/consts/trajectoryMore.ts +++ b/projects/core/src/consts/lists/trajectory-more-list.const.ts @@ -1,6 +1,6 @@ /** @format */ -export const trajectoryMore = [ +export const trajectoryMoreList = [ { label: "Работа с наставником", }, diff --git a/projects/core/src/consts/list-experience.ts b/projects/core/src/consts/lists/work-experience-list.const.ts similarity index 91% rename from projects/core/src/consts/list-experience.ts rename to projects/core/src/consts/lists/work-experience-list.const.ts index 5388df8b0..6fbc0038c 100644 --- a/projects/core/src/consts/list-experience.ts +++ b/projects/core/src/consts/lists/work-experience-list.const.ts @@ -1,6 +1,6 @@ /** @format */ -export const experienceList = [ +export const workExperienceList = [ { id: 0, value: "без опыта", diff --git a/projects/core/src/consts/list-format.ts b/projects/core/src/consts/lists/work-format-list.const.ts similarity index 90% rename from projects/core/src/consts/list-format.ts rename to projects/core/src/consts/lists/work-format-list.const.ts index a00d1ad30..663eeb688 100644 --- a/projects/core/src/consts/list-format.ts +++ b/projects/core/src/consts/lists/work-format-list.const.ts @@ -1,6 +1,6 @@ /** @format */ -export const formatList = [ +export const workFormatList = [ { id: 0, value: "удаленная работа", diff --git a/projects/core/src/consts/list-schelude.ts b/projects/core/src/consts/lists/work-schelude-list.const.ts similarity index 94% rename from projects/core/src/consts/list-schelude.ts rename to projects/core/src/consts/lists/work-schelude-list.const.ts index 19217586a..df6f33a95 100644 --- a/projects/core/src/consts/list-schelude.ts +++ b/projects/core/src/consts/lists/work-schelude-list.const.ts @@ -1,6 +1,6 @@ /** @format */ -export const scheludeList = [ +export const workScheludeList = [ { id: 0, value: "полный рабочий день", diff --git a/projects/core/src/consts/navProfileItems.ts b/projects/core/src/consts/navigation/nav-profile-items.const.ts similarity index 94% rename from projects/core/src/consts/navProfileItems.ts rename to projects/core/src/consts/navigation/nav-profile-items.const.ts index 2b1aa46b3..cba732394 100644 --- a/projects/core/src/consts/navProfileItems.ts +++ b/projects/core/src/consts/navigation/nav-profile-items.const.ts @@ -1,6 +1,6 @@ /** @format */ -export const navItems = [ +export const navProfileItems = [ { step: "main", src: "main", diff --git a/projects/core/src/consts/navProjectItems.ts b/projects/core/src/consts/navigation/nav-project-items.const.ts similarity index 96% rename from projects/core/src/consts/navProjectItems.ts rename to projects/core/src/consts/navigation/nav-project-items.const.ts index d396b7509..21f82b8d4 100644 --- a/projects/core/src/consts/navProjectItems.ts +++ b/projects/core/src/consts/navigation/nav-project-items.const.ts @@ -6,7 +6,7 @@ import { EditStep } from "@office/projects/edit/services/project-step.service"; * Элементы навигации для редактирования проекта * Используется в компоненте пошагового редактирования проекта */ -export const navItems = [ +export const navProjectItems = [ { step: "main" as EditStep, // Идентификатор шага label: "основные данные", // Отображаемый текст diff --git a/projects/core/src/consts/fieldsProfile.ts b/projects/core/src/consts/other/profile-fields.const.ts similarity index 97% rename from projects/core/src/consts/fieldsProfile.ts rename to projects/core/src/consts/other/profile-fields.const.ts index b6875cd17..7e571629e 100644 --- a/projects/core/src/consts/fieldsProfile.ts +++ b/projects/core/src/consts/other/profile-fields.const.ts @@ -5,7 +5,7 @@ * Определяет какие поля являются массивами, а какие строками * Используется для валидации и обработки данных профиля */ -export const fieldsProfile = [ +export const profileFields = [ // Поля-массивы (содержат несколько элементов) { key: "education", type: "array" }, // Образование { key: "workExperience", type: "array" }, // Опыт работы diff --git a/projects/skills/src/app/profile/home/profile-home.component.ts b/projects/skills/src/app/profile/home/profile-home.component.ts index dd71e0291..f8be4426c 100644 --- a/projects/skills/src/app/profile/home/profile-home.component.ts +++ b/projects/skills/src/app/profile/home/profile-home.component.ts @@ -8,7 +8,7 @@ import { ProgressBlockComponent } from "../shared/progress-block/progress-block. import { ActivatedRoute } from "@angular/router"; import { toSignal } from "@angular/core/rxjs-interop"; import { map, type Subscription } from "rxjs"; -import { mockMonthsList } from "projects/core/src/consts/list-mock-months"; +import { mockMonthsList } from "projects/core/src/consts/lists/mock-months-list.const"; import { ProfileService } from "../services/profile.service"; import { TrajectoryBlockComponent } from "../shared/trajectory-block/trajectory-block.component"; diff --git a/projects/skills/src/app/rating/general/general.component.ts b/projects/skills/src/app/rating/general/general.component.ts index 5d7f312a6..5b08a4f45 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/rating-filters"; +import { ratingFiltersList } from "projects/core/src/consts/filters/rating-filter.const"; /** * Компонент общего рейтинга пользователей diff --git a/projects/skills/src/app/trajectories/track-career/shared/trajectory/trajectory.component.ts b/projects/skills/src/app/trajectories/track-career/shared/trajectory/trajectory.component.ts index e0def68b5..ab631b0f4 100644 --- a/projects/skills/src/app/trajectories/track-career/shared/trajectory/trajectory.component.ts +++ b/projects/skills/src/app/trajectories/track-career/shared/trajectory/trajectory.component.ts @@ -21,7 +21,7 @@ import { expandElement } from "@utils/expand-element"; import { IconComponent } from "@uilib"; import { ParseBreaksPipe, ParseLinksPipe, PluralizePipe } from "@corelib"; import { Trajectory } from "projects/skills/src/models/trajectory.model"; -import { trajectoryMore } from "projects/core/src/consts/trajectoryMore"; +import { trajectoryMore } from "projects/core/src/consts/lists/trajectory-more-list.const"; import { HttpErrorResponse } from "@angular/common/http"; import { BreakpointObserver } from "@angular/cdk/layout"; import { map, Observable } from "rxjs"; diff --git a/projects/social_platform/src/app/office/features/approve-skill/approve-skill.component.html b/projects/social_platform/src/app/office/features/approve-skill/approve-skill.component.html new file mode 100644 index 000000000..a2ed58313 --- /dev/null +++ b/projects/social_platform/src/app/office/features/approve-skill/approve-skill.component.html @@ -0,0 +1,31 @@ + + +
      + {{ skill.name }} +
      + @if (skill.approves.length > 0) { + + } + + {{ isUserApproveSkill(skill, loggedUserId!) ? "убрать оценку" : "подтвердить" }} +
      +
      + + + + diff --git a/projects/social_platform/src/app/office/features/approve-skill/approve-skill.component.scss b/projects/social_platform/src/app/office/features/approve-skill/approve-skill.component.scss new file mode 100644 index 000000000..203d7b070 --- /dev/null +++ b/projects/social_platform/src/app/office/features/approve-skill/approve-skill.component.scss @@ -0,0 +1,20 @@ +.approve { + &__skill { + display: flex; + flex-direction: column; + gap: 8px; + padding: 12px; + border: 0.5px solid var(--medium-grey-for-outline); + border-radius: var(--rounded-xl); + } + + &__text { + color: var(--dark-grey); + } + + &__approves { + display: flex; + align-items: center; + justify-content: space-between; + } +} diff --git a/projects/social_platform/src/app/office/features/approve-skill/approve-skill.component.ts b/projects/social_platform/src/app/office/features/approve-skill/approve-skill.component.ts new file mode 100644 index 000000000..3a1db27d9 --- /dev/null +++ b/projects/social_platform/src/app/office/features/approve-skill/approve-skill.component.ts @@ -0,0 +1,128 @@ +/** @format */ + +import { CommonModule } from "@angular/common"; +import { ChangeDetectorRef, Component, inject, Input, OnDestroy, OnInit } from "@angular/core"; +import { PluralizePipe } from "@corelib"; +import { Skill } from "@office/models/skill"; +import { AvatarComponent } from "@ui/components/avatar/avatar.component"; +import { ButtonComponent } from "@ui/components"; +import { map, of, Subscription, switchMap } from "rxjs"; +import { AuthService } from "@auth/services"; +import { ActivatedRoute } from "@angular/router"; +import { ProfileService } from "projects/skills/src/app/profile/services/profile.service"; +import { ProfileService as profileApproveSkillService } from "@auth/services/profile.service"; +import { SnackbarService } from "@ui/services/snackbar.service"; +import { HttpErrorResponse } from "@angular/common/http"; +import { ModalComponent } from "@ui/components/modal/modal.component"; +import { ApproveSkillPeopleComponent } from "@office/shared/approve-skill-people/approve-skill-people.component"; + +/** + * @params skill - информация о навыке (обязательно) + * + * Компонент на основе полученных данных о навыке + * выполняет логику подтверждения навыка + * с помощью сервисов связанных с навыками пользователя + */ +@Component({ + selector: "app-approve-skill", + styleUrl: "./approve-skill.component.scss", + templateUrl: "./approve-skill.component.html", + standalone: true, + imports: [ + CommonModule, + AvatarComponent, + PluralizePipe, + ButtonComponent, + ModalComponent, + ApproveSkillPeopleComponent, + ], +}) +export class ApproveSkillComponent implements OnInit, OnDestroy { + private readonly authService = inject(AuthService); + private readonly route = inject(ActivatedRoute); + private readonly profileApproveSkillService = inject(profileApproveSkillService); + private readonly snackbarService = inject(SnackbarService); + private readonly cdRef = inject(ChangeDetectorRef); + + // Указатель на то что пользватель подтвердил навык + isUserApproveSkill(skill: Skill, profileId: number): boolean { + return skill.approves.some(approve => approve.confirmedBy.id === profileId); + } + + // id пользователя за которого мы зарегистрировались + loggedUserId?: number; + + // переменные для работы с модальным окном для вывода ошибки с подтверждением своего навыка + approveOwnSkillModal = false; + + subscriptions: Subscription[] = []; + + // Получение данных о конкретном навыке + @Input({ required: true }) skill!: Skill; + + ngOnInit(): void { + const profileIdDataSub$ = this.authService.profile.pipe().subscribe({ + next: profile => { + this.loggedUserId = profile.id; + }, + }); + + this.subscriptions.push(profileIdDataSub$); + } + + ngOnDestroy(): void { + this.subscriptions.forEach($ => $.unsubscribe()); + } + + /** + * Подтверждение или отмена подтверждения навыка пользователя + * @param skillId - идентификатор навыка + * @param event - событие клика для предотвращения всплытия + * @param skill - объект навыка для обновления + */ + onToggleApprove(skillId: number, event: Event, skill: Skill, profileId: number) { + event.stopPropagation(); + const userId = this.route.snapshot.params["id"]; + + const isApprovedByCurrentUser = skill.approves.some(approve => { + return approve.confirmedBy.id === profileId; + }); + + if (isApprovedByCurrentUser) { + this.profileApproveSkillService.unApproveSkill(userId, skillId).subscribe(() => { + skill.approves = skill.approves.filter(approve => approve.confirmedBy.id !== profileId); + this.cdRef.markForCheck(); + }); + } else { + this.profileApproveSkillService + .approveSkill(userId, skillId) + .pipe( + switchMap(newApprove => + newApprove.confirmedBy + ? of(newApprove) + : this.authService.profile.pipe( + map(profile => ({ + ...newApprove, + confirmedBy: profile, + })) + ) + ) + ) + .subscribe({ + next: updatedApprove => { + skill.approves = [...skill.approves, updatedApprove]; + this.snackbarService.success("вы подтвердили навык"); + this.cdRef.markForCheck(); + }, + error: err => { + if (err instanceof HttpErrorResponse) { + if (err.status === 400) { + this.approveOwnSkillModal = true; + this.cdRef.markForCheck(); + } + } + }, + }); + } + } +} 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 501df92db..9e308f3ad 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 @@ -514,70 +514,12 @@

      подтвердить владение н @for (skill of info().skills; track skill.id) {
    • -
      - {{ skill.name }} -
      - @if (skill.approves.length > 0) { -
      - @if (skill.approves.length >= 3) { @for (approve of skill.approves.slice(0, - 3); track $index) { - - } } @else { @for (approve of skill.approves; track $index) { - - } } @if (skill.approves.length >= 3) { -

      - и ещё {{ skill.approves.length - 3 + "подтвердили" }} -

      - } @else { -

      - {{ - skill.approves.length + - " " + - (skill.approves.length | pluralize: ["человек", "человека", "человек"]) - }} -

      - } -
      - - } - - {{ - isUserApproveSkill(skill, loggedUserId!) ? "убрать оценку" : "подтвердить" - }} -
      -
      +
    • }

    } - - - - } @if (profile) { @if (profile.id === info().id) {
    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 dcfd81f42..86288168f 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 @@ -192,36 +192,6 @@ $detail-bar-mb: 12px; .approve { min-width: 420px; padding: 24px; - - &__skill { - display: flex; - flex-direction: column; - gap: 8px; - padding: 12px; - border: 0.5px solid var(--medium-grey-for-outline); - border-radius: var(--rounded-xl); - } - - &__text { - color: var(--dark-grey); - } - - &__approves { - display: flex; - align-items: center; - justify-content: space-between; - - &--people { - display: flex; - gap: 4px; - align-items: center; - margin-left: 12px; - - app-avatar { - margin-left: -12px; - } - } - } } .cancel { 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 cbd202e97..28fb75f2b 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 @@ -9,7 +9,7 @@ import { ActivatedRoute, Router, RouterModule } from "@angular/router"; import { AuthService } from "@auth/services"; import { AvatarComponent } from "@ui/components/avatar/avatar.component"; import { TooltipComponent } from "@ui/components/tooltip/tooltip.component"; -import { concatMap, filter, map, of, Subscription, switchMap, take, tap } from "rxjs"; +import { concatMap, filter, map, of, Subscription, tap } from "rxjs"; import { User } from "@auth/models/user.model"; import { Collaborator } from "@office/models/collaborator.model"; import { ProjectService } from "@office/services/project.service"; @@ -23,10 +23,9 @@ import { ChatService } from "@office/services/chat.service"; import { calculateProfileProgress } from "@utils/calculateProgress"; import { ProfileDataService } from "@office/profile/detail/services/profile-date.service"; import { ProfileService } from "projects/skills/src/app/profile/services/profile.service"; -import { ProfileService as profileApproveSkillService } from "@auth/services/profile.service"; import { SnackbarService } from "@ui/services/snackbar.service"; -import { Skill } from "@office/models/skill"; import { PluralizePipe } from "@corelib"; +import { ApproveSkillComponent } from "../approve-skill/approve-skill.component"; @Component({ selector: "app-detail", @@ -42,7 +41,7 @@ import { PluralizePipe } from "@corelib"; AvatarComponent, TooltipComponent, InputComponent, - PluralizePipe, + ApproveSkillComponent, ], standalone: true, }) @@ -53,7 +52,6 @@ export class DeatilComponent implements OnInit, OnDestroy { private readonly programDataService = inject(ProgramDataService); private readonly projectDataService = inject(ProjectDataService); private readonly projectAdditionalService = inject(ProjectAdditionalService); - private readonly profileApproveSkillService = inject(profileApproveSkillService); private readonly snackbarService = inject(SnackbarService); private readonly router = inject(Router); private readonly location = inject(Location); @@ -64,7 +62,6 @@ export class DeatilComponent implements OnInit, OnDestroy { // Основные данные(типы данных, данные) info = signal(undefined); - loggedUserId?: number; profile?: User; listType: "project" | "program" | "profile" = "project"; @@ -113,7 +110,6 @@ export class DeatilComponent implements OnInit, OnDestroy { // Переменные для работы с подтверждением навыков showApproveSkillModal = false; readAllModal = false; - approveOwnSkillModal = false; subscriptions: Subscription[] = []; @@ -292,10 +288,6 @@ export class DeatilComponent implements OnInit, OnDestroy { }); } - isUserApproveSkill(skill: Skill, profileId: number): boolean { - return skill.approves.some(approve => approve.confirmedBy.id === profileId); - } - openSkills: any = {}; /** @@ -310,58 +302,6 @@ export class DeatilComponent implements OnInit, OnDestroy { this.openSkills[skillId] = false; } - /** - * Подтверждение или отмена подтверждения навыка пользователя - * @param skillId - идентификатор навыка - * @param event - событие клика для предотвращения всплытия - * @param skill - объект навыка для обновления - */ - onToggleApprove(skillId: number, event: Event, skill: Skill, profileId: number) { - event.stopPropagation(); - const userId = this.route.snapshot.params["id"]; - - const isApprovedByCurrentUser = skill.approves.some(approve => { - return approve.confirmedBy.id === profileId; - }); - - if (isApprovedByCurrentUser) { - this.profileApproveSkillService.unApproveSkill(userId, skillId).subscribe(() => { - skill.approves = skill.approves.filter(approve => approve.confirmedBy.id !== profileId); - this.cdRef.markForCheck(); - }); - } else { - this.profileApproveSkillService - .approveSkill(userId, skillId) - .pipe( - switchMap(newApprove => - newApprove.confirmedBy - ? of(newApprove) - : this.authService.profile.pipe( - map(profile => ({ - ...newApprove, - confirmedBy: profile, - })) - ) - ) - ) - .subscribe({ - next: updatedApprove => { - skill.approves = [...skill.approves, updatedApprove]; - this.snackbarService.success("вы подтвердили навык"); - this.cdRef.markForCheck(); - }, - error: err => { - if (err instanceof HttpErrorResponse) { - if (err.status === 400) { - this.approveOwnSkillModal = true; - this.cdRef.markForCheck(); - } - } - }, - }); - } - } - /** * Отправка CV пользователя на email * Проверяет ограничения по времени и отправляет CV на почту пользователя @@ -470,17 +410,11 @@ export class DeatilComponent implements OnInit, OnDestroy { this.isInProfileInfo(); - const profileIdDataSub$ = this.authService.profile.pipe().subscribe({ - next: profile => { - this.loggedUserId = profile.id; - }, - }); - this.skillsProfileService.getSubscriptionData().subscribe(r => { this.isSubscriptionActive.set(r.isSubscribed); }); - this.subscriptions.push(profileDataSub$, profileIdDataSub$); + this.subscriptions.push(profileDataSub$); } } diff --git a/projects/social_platform/src/app/office/features/invite-card/invite-card.component.ts b/projects/social_platform/src/app/office/features/invite-card/invite-card.component.ts index d4e982992..d918309b5 100644 --- a/projects/social_platform/src/app/office/features/invite-card/invite-card.component.ts +++ b/projects/social_platform/src/app/office/features/invite-card/invite-card.component.ts @@ -7,7 +7,7 @@ import { ErrorMessage } from "@error/models/error-message"; import { Invite } from "@models/invite.model"; import { IconComponent, ButtonComponent, SelectComponent, InputComponent } from "@ui/components"; import { ModalComponent } from "@ui/components/modal/modal.component"; -import { rolesMembersList } from "projects/core/src/consts/list-roles-members"; +import { rolesMembersList } from "projects/core/src/consts/lists/roles-members-list.const"; import { AvatarComponent } from "@ui/components/avatar/avatar.component"; /** diff --git a/projects/social_platform/src/app/office/feed/feed.component.scss b/projects/social_platform/src/app/office/feed/feed.component.scss index 48137976f..cf6a935c2 100644 --- a/projects/social_platform/src/app/office/feed/feed.component.scss +++ b/projects/social_platform/src/app/office/feed/feed.component.scss @@ -4,6 +4,7 @@ display: flex; flex-direction: column; gap: 20px; + padding-top: 40px; &__feed { column-count: 2; diff --git a/projects/social_platform/src/app/office/feed/filter/feed-filter.component.html b/projects/social_platform/src/app/office/feed/filter/feed-filter.component.html index 4c9eb88be..c559a9872 100644 --- a/projects/social_platform/src/app/office/feed/filter/feed-filter.component.html +++ b/projects/social_platform/src/app/office/feed/filter/feed-filter.component.html @@ -31,7 +31,7 @@
    - @for (filterItem of filterOptions; track $index) { + @for (filterItem of feedFilterOptions; track $index) {
    (""); diff --git a/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.ts b/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.ts index 24e389d6b..f2fc30309 100644 --- a/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.ts +++ b/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.ts @@ -12,8 +12,14 @@ import { OnboardingService } from "../services/onboarding.service"; import { ButtonComponent, InputComponent, SelectComponent } from "@ui/components"; import { AvatarControlComponent } from "@ui/components/avatar-control/avatar-control.component"; import { CommonModule } from "@angular/common"; -import { educationUserLevel, educationUserType } from "projects/core/src/consts/list-education"; -import { languageLevelsList, languageNamesList } from "projects/core/src/consts/list-language"; +import { + educationUserLevel, + educationUserType, +} from "projects/core/src/consts/lists/education-info-list.const"; +import { + languageLevelsList, + languageNamesList, +} from "projects/core/src/consts/lists/language-info-list.const"; import { IconComponent } from "@uilib"; import { transformYearStringToNumber } from "@utils/transformYear"; import { yearRangeValidators } from "@utils/yearRangeValidators"; diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.html b/projects/social_platform/src/app/office/profile/edit/edit.component.html index 1a39eb9ec..4ed32456b 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.html +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.html @@ -54,7 +54,7 @@

    редактирование профиля

      - @for (item of navItems; track $index) { + @for (item of navProfileItems; track $index) {
    • this.authService.getProfile())) .subscribe({ - next: res => { + next: () => { this.profileFormSubmitting = false; - console.log(res); - // this.router - // .navigateByUrl(`/office/profile/${this.profileId}`) - // .then(() => console.debug("Router Changed form ProfileEditComponent")); + this.router + .navigateByUrl(`/office/profile/${this.profileId}`) + .then(() => console.debug("Router Changed form ProfileEditComponent")); }, error: error => { this.profileFormSubmitting = false; diff --git a/projects/social_platform/src/app/office/program/detail/list/list.component.ts b/projects/social_platform/src/app/office/program/detail/list/list.component.ts index f7784e0e2..ffe667cf6 100644 --- a/projects/social_platform/src/app/office/program/detail/list/list.component.ts +++ b/projects/social_platform/src/app/office/program/detail/list/list.component.ts @@ -43,8 +43,8 @@ import { ApiPagination } from "@models/api-pagination.model"; import { HttpParams } from "@angular/common/http"; import { PartnerProgramFields } from "@office/models/partner-program-fields.model"; import { CheckboxComponent } from "@ui/components"; -import { filterTags } from "projects/core/src/consts/filter-tags"; import { InfoCardComponent } from "@office/features/info-card/info-card.component"; +import { tagsFilter } from "projects/core/src/consts/filters/tags-filter.const"; @Component({ selector: "app-list", @@ -119,7 +119,7 @@ export class ProgramListComponent implements OnInit, OnDestroy, AfterViewInit { listType: "projects" | "members" | "rating" = "projects"; - readonly ratingOptionsList = filterTags; + readonly ratingOptionsList = tagsFilter; isFilterOpen = false; subscriptions$: Subscription[] = []; diff --git a/projects/social_platform/src/app/office/projects/edit/services/project-vacancy.service.ts b/projects/social_platform/src/app/office/projects/edit/services/project-vacancy.service.ts index 586bba823..997a65f58 100644 --- a/projects/social_platform/src/app/office/projects/edit/services/project-vacancy.service.ts +++ b/projects/social_platform/src/app/office/projects/edit/services/project-vacancy.service.ts @@ -8,11 +8,11 @@ import { Skill } from "@office/models/skill"; import { Vacancy } from "@office/models/vacancy.model"; import { VacancyService } from "@office/services/vacancy.service"; import { stripNullish } from "@utils/stripNull"; -import { experienceList } from "projects/core/src/consts/list-experience"; -import { formatList } from "projects/core/src/consts/list-format"; -import { scheludeList } from "projects/core/src/consts/list-schelude"; -import { rolesMembersList } from "projects/core/src/consts/list-roles-members"; +import { rolesMembersList } from "projects/core/src/consts/lists/roles-members-list.const"; import { ProjectFormService } from "./project-form.service"; +import { workExperienceList } from "projects/core/src/consts/lists/work-experience-list.const"; +import { workFormatList } from "projects/core/src/consts/lists/work-format-list.const"; +import { workScheludeList } from "projects/core/src/consts/lists/work-schelude-list.const"; /** * Сервис для управления вакансиями проекта. @@ -31,9 +31,9 @@ export class ProjectVacancyService { private readonly validationService = inject(ValidationService); /** Константы для выпадающих списков */ - public readonly experienceList = experienceList; - public readonly formatList = formatList; - public readonly scheludeList = scheludeList; + public readonly workExperienceList = workExperienceList; + public readonly workFormatList = workFormatList; + public readonly workScheludeList = workScheludeList; public readonly rolesMembersList = rolesMembersList; /** Сигналы для выбранных значений селектов */ @@ -242,20 +242,24 @@ export class ProjectVacancyService { public editVacancy(index: number): void { const item = this.vacancies()[index]; // Установка выбранных значений селектов по сопоставлению - this.experienceList.find(e => e.value === item.requiredExperience) && + this.workExperienceList.find(e => e.value === item.requiredExperience) && this.selectedRequiredExperienceId.set( - this.experienceList.find(e => e.value === item.requiredExperience)!.id + this.workExperienceList.find(e => e.value === item.requiredExperience)!.id ); - this.formatList.find(f => f.value === item.workFormat) && - this.selectedWorkFormatId.set(this.formatList.find(f => f.value === item.workFormat)!.id); - this.scheludeList.find(s => s.value === item.workSchedule) && + + this.workFormatList.find(f => f.value === item.workFormat) && + this.selectedWorkFormatId.set(this.workFormatList.find(f => f.value === item.workFormat)!.id); + + this.workScheludeList.find(s => s.value === item.workSchedule) && this.selectedWorkScheduleId.set( - this.scheludeList.find(s => s.value === item.workSchedule)!.id + this.workScheludeList.find(s => s.value === item.workSchedule)!.id ); + this.rolesMembersList.find(r => r.value === item.specialization) && this.selectedVacanciesSpecializationId.set( this.rolesMembersList.find(r => r.value === item.specialization)!.id ); + // Патчинг формы значениями вакансии this.vacancyForm.patchValue({ role: item.role, diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.ts b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.ts index d37594204..72ae92bd9 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.ts +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-main-step/project-main-step.component.ts @@ -20,8 +20,8 @@ import { } from "@angular/forms"; import { AuthService } from "@auth/services"; import { ErrorMessage } from "@error/models/error-message"; -import { directionProjectList } from "projects/core/src/consts/list-direction-project"; -import { trackProjectList } from "projects/core/src/consts/list-track-project"; +import { directionProjectList } from "projects/core/src/consts/lists/ldirection-project-list.const"; +import { trackProjectList } from "projects/core/src/consts/lists/track-project-list.const"; import { Observable, Subscription } from "rxjs"; import { AvatarControlComponent } from "@ui/components/avatar-control/avatar-control.component"; import { InputComponent, SelectComponent, ButtonComponent } from "@ui/components"; diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.html b/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.html index b233a9513..87246e721 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.html +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-navigation/project-navigation.component.html @@ -2,7 +2,7 @@
    - } @else { } } + } @else { +
      + @if (!isOpenInfo) { @for (aboutInfo of about; track $index) { +
    • +

      {{ aboutInfo.year }}

      + +
    • + } } @else { +
      +
      + +

      {{ currentYear }}

      +
      + + @if (achievementsInfo().length > 0) { @for (achievementInfo of achievementsInfo(); track + $index) { +
      +

      {{ achievementInfo.title }}

      +

      {{ achievementInfo.status }}

      + + @if (files.length > 0) { + + } +
      + } } +
      + } +
    + } }
    diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss index 556d35323..6c7ea2002 100644 --- a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss @@ -67,7 +67,7 @@ color: var(--black); } - &__skill { + &__skill, &__achievements { display: flex; flex-direction: column; gap: 8px; @@ -77,6 +77,62 @@ grid-template-columns: 2fr 2fr; grid-gap: 20px; } + + &--year { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px; + width: 100%; + background-color: var(--white); + border: 0.5px solid var(--medium-grey-for-outline); + border-radius: var(--rounded-lg); + cursor: pointer; + + p { + color: var(--black); + } + + i, svg { + color: var(--grey-for-text); + transform: rotate(90deg); + } + } + } + + &__achievement { + display: flex; + flex-direction: column; + gap: 8px; + + &--year { + display: flex; + align-items: center; + gap: 10px; + cursor: pointer; + color: var(--grey-for-text); + + i { + transform: rotate(-90deg); + } + } + + &--title { + color: var(--black); + } + + &--status { + color: var(--grey-for-text); + } + + &--info { + display: flex; + flex-direction: column; + gap: 2px; + padding: 12px; + border-radius: var(--rounded-lg); + border: 0.5px solid var(--medium-grey-for-outline); + } } } diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.ts b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.ts index 1a494f1bc..e87620f54 100644 --- a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.ts +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.ts @@ -1,19 +1,24 @@ /** @format */ import { CommonModule } from "@angular/common"; -import { Component, Input } from "@angular/core"; +import { Component, inject, Input, OnDestroy, OnInit, signal } from "@angular/core"; import { ModalComponent } from "@ui/components/modal/modal.component"; import { IconComponent } from "@uilib"; import { TagComponent } from "@ui/components/tag/tag.component"; - +import { ActivatedRoute, Router } from "@angular/router"; +import { ProfileService } from "@auth/services/profile.service"; +import { map, Subscription, switchMap } from "rxjs"; +import { Achievement } from "@auth/models/user.model"; +import { FileItemComponent } from "@ui/components/file-item/file-item.component"; +import { FileModel } from "@office/models/file.model"; @Component({ selector: "app-project-direction-card", templateUrl: "./project-direction-card.component.html", styleUrl: "./project-direction-card.component.scss", - imports: [CommonModule, IconComponent, ModalComponent, TagComponent], + imports: [CommonModule, IconComponent, ModalComponent, TagComponent, FileItemComponent], standalone: true, }) -export class ProjectDirectionCard { +export class ProjectDirectionCard implements OnInit, OnDestroy { @Input() direction!: string; @Input() icon!: string; @Input() about!: string | any[]; @@ -21,5 +26,84 @@ export class ProjectDirectionCard { @Input() profileInfoType?: "skills" | "achievements" = "skills"; + private readonly route = inject(ActivatedRoute); + private readonly router = inject(Router); + private readonly achievementsService = inject(ProfileService); + + private subscriptions: Subscription[] = []; + isShowModal = false; + isOpenInfo = false; + + achievements: Pick[] = []; + files: FileModel[] = []; + achievementsInfo = signal([]); + currentYear = 0; + + ngOnInit(): void { + if (this.profileInfoType === "achievements") { + if (Array.isArray(this.about)) { + this.about = Array.from( + new Map(this.about.map(a => [a.year, { id: a.id, year: a.year }])).values() + ); + } + this.getAchievementsByYear(); + } + } + + ngOnDestroy(): void { + this.subscriptions.forEach($ => $.unsubscribe()); + } + + openInfo(achievementYear: string): void { + this.router.navigate([], { + queryParams: { + year: achievementYear, + }, + relativeTo: this.route, + queryParamsHandling: "merge", + }); + } + + backToYears(): void { + this.router.navigate([], { + queryParams: {}, + replaceUrl: true, + }); + + this.isOpenInfo = false; + } + + private getAchievementsByYear(): void { + const infoParamSub$ = this.route.queryParams + .pipe( + map(p => p["year"]), + switchMap(year => { + if (year) { + this.isOpenInfo = true; + this.currentYear = year; + return this.achievementsService + .getAchievements() + .pipe( + map((achievements: Achievement[]) => + achievements.filter((achievement: Achievement) => +achievement.year === +year) + ) + ); + } else { + this.isOpenInfo = false; + this.achievementsInfo.set([]); + return []; + } + }) + ) + .subscribe({ + next: achievements => { + this.achievementsInfo.set(achievements); + + this.files = achievements.flatMap(a => (a.files ?? []) as FileModel[]); + }, + }); + + this.subscriptions.push(infoParamSub$); + } } diff --git a/projects/social_platform/src/app/ui/components/file-item/file-item.component.html b/projects/social_platform/src/app/ui/components/file-item/file-item.component.html index 58111babb..e2ba2ecad 100644 --- a/projects/social_platform/src/app/ui/components/file-item/file-item.component.html +++ b/projects/social_platform/src/app/ui/components/file-item/file-item.component.html @@ -1,7 +1,14 @@ +@if (name && link) {
    - +
    @@ -15,4 +22,15 @@
    }
    + + @if (canDelete) { + + }
    +} diff --git a/projects/social_platform/src/app/ui/components/file-item/file-item.component.scss b/projects/social_platform/src/app/ui/components/file-item/file-item.component.scss index a94329efc..9eff34b16 100644 --- a/projects/social_platform/src/app/ui/components/file-item/file-item.component.scss +++ b/projects/social_platform/src/app/ui/components/file-item/file-item.component.scss @@ -3,6 +3,7 @@ .file { display: flex; align-items: center; + gap: 10px; &__name { overflow: hidden; @@ -19,20 +20,24 @@ color: var(--dark-grey); } - &__download { + &__icon { display: flex; align-items: center; align-self: center; justify-content: center; width: 20px; height: 20px; - margin-right: 6px; border: 0.5px solid var(--medium-grey-for-outline); border-radius: var(--rounded-xl); - } - i { - color: var(--accent); - cursor: pointer; + &--delete { + color: var(--red); + cursor: pointer; + } + + &--download { + color: var(--accent); + cursor: pointer; + } } } diff --git a/projects/social_platform/src/app/ui/components/file-item/file-item.component.ts b/projects/social_platform/src/app/ui/components/file-item/file-item.component.ts index 2941d1913..79d9d9d64 100644 --- a/projects/social_platform/src/app/ui/components/file-item/file-item.component.ts +++ b/projects/social_platform/src/app/ui/components/file-item/file-item.component.ts @@ -1,9 +1,10 @@ /** @format */ -import { Component, Input, OnInit } from "@angular/core"; +import { Component, inject, Input, OnInit } from "@angular/core"; import { FileTypePipe } from "@ui/pipes/file-type.pipe"; import { IconComponent } from "@ui/components"; import { UpperCasePipe } from "@angular/common"; import { FormatedFileSizePipe } from "@core/pipes/formatted-file-size.pipe"; +import { FileService } from "@core/services/file.service"; /** * Компонент для отображения информации о файле. @@ -28,7 +29,9 @@ import { FormatedFileSizePipe } from "@core/pipes/formatted-file-size.pipe"; imports: [IconComponent, FileTypePipe, UpperCasePipe, FormatedFileSizePipe], }) export class FileItemComponent implements OnInit { - constructor() {} + private readonly fileService = inject(FileService); + + @Input() canDelete = false; /** MIME-тип файла */ @Input() type = "file"; @@ -55,4 +58,17 @@ export class FileItemComponent implements OnInit { link.click(); document.body.removeChild(link); } + + /** + * Удаление файла + * Удаляет файл с сервера и из списка прикрепленных файлов + */ + onDeleteFile(): void { + if (!this.link) return; + + this.fileService.deleteFile(this.link).subscribe(() => { + this.link = ""; + this.name = ""; + }); + } } From 91b6be18f9833eb2e6a2fb3ef7866372d014ab63 Mon Sep 17 00:00:00 2001 From: Awakich Date: Tue, 28 Oct 2025 18:24:11 +0300 Subject: [PATCH 115/126] add styles for user achievement modal, file-item --- .../new-project/new-project.component.scss | 2 +- .../project-direction-card.component.scss | 16 +++++++++------- .../file-item/file-item.component.scss | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.scss b/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.scss index e794960cd..c74734080 100644 --- a/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.scss +++ b/projects/social_platform/src/app/office/feed/shared/new-project/new-project.component.scss @@ -10,8 +10,8 @@ &__avatar { display: flex; align-items: center; - text-align: center; justify-content: center; + text-align: center; } &__project { diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss index 6c7ea2002..d1f09c316 100644 --- a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss @@ -67,7 +67,8 @@ color: var(--black); } - &__skill, &__achievements { + &__skill, + &__achievements { display: flex; flex-direction: column; gap: 8px; @@ -82,18 +83,19 @@ display: flex; align-items: center; justify-content: space-between; - padding: 10px; width: 100%; + padding: 10px; + cursor: pointer; background-color: var(--white); border: 0.5px solid var(--medium-grey-for-outline); border-radius: var(--rounded-lg); - cursor: pointer; p { color: var(--black); } - i, svg { + i, + svg { color: var(--grey-for-text); transform: rotate(90deg); } @@ -107,10 +109,10 @@ &--year { display: flex; - align-items: center; gap: 10px; - cursor: pointer; + align-items: center; color: var(--grey-for-text); + cursor: pointer; i { transform: rotate(-90deg); @@ -130,8 +132,8 @@ flex-direction: column; gap: 2px; padding: 12px; - border-radius: var(--rounded-lg); border: 0.5px solid var(--medium-grey-for-outline); + border-radius: var(--rounded-lg); } } } diff --git a/projects/social_platform/src/app/ui/components/file-item/file-item.component.scss b/projects/social_platform/src/app/ui/components/file-item/file-item.component.scss index 9eff34b16..6efc7cf0b 100644 --- a/projects/social_platform/src/app/ui/components/file-item/file-item.component.scss +++ b/projects/social_platform/src/app/ui/components/file-item/file-item.component.scss @@ -2,8 +2,8 @@ .file { display: flex; - align-items: center; gap: 10px; + align-items: center; &__name { overflow: hidden; From e7d0cd6b76c83fad9823920b48b478939586817c Mon Sep 17 00:00:00 2001 From: Awakich Date: Wed, 29 Oct 2025 16:35:39 +0300 Subject: [PATCH 116/126] fix design in feed-filter, edit profile, skills-group modal, direction-card, open-vacancy-card, add modals for parter, goals, resources --- .../src/consts/filters/rating-filter.const.ts | 8 +- .../filters/work-experience-filter.const.ts | 8 +- .../filters/work-format-filter.const.ts | 6 +- .../filters/work-schedule-filter.const.ts | 10 +- .../consts/lists/education-info-list.const.ts | 16 +- .../consts/lists/language-info-list.const.ts | 24 +-- .../lists/ldirection-project-list.const.ts | 14 +- .../lists/resource-options-list.const.ts | 8 +- .../consts/lists/roles-members-list.const.ts | 14 +- .../consts/lists/track-project-list.const.ts | 10 +- .../lists/work-experience-list.const.ts | 8 +- .../consts/lists/work-format-list.const.ts | 6 +- .../consts/lists/work-schelude-list.const.ts | 10 +- .../features/detail/detail.component.html | 19 ++- .../features/detail/detail.component.scss | 1 + .../features/detail/detail.component.ts | 2 - .../feed/filter/feed-filter.component.scss | 1 + .../open-vacancy/open-vacancy.component.html | 15 +- .../open-vacancy/open-vacancy.component.scss | 2 +- .../src/app/office/models/project.model.ts | 9 ++ .../src/app/office/models/resource.model.ts | 2 +- .../office/profile/edit/edit.component.html | 145 ++++++++++-------- .../office/profile/edit/edit.component.scss | 7 + .../app/office/profile/edit/edit.component.ts | 21 +++ .../projects/detail/info/info.component.html | 29 ++-- .../projects/detail/info/info.component.ts | 4 +- .../project-direction-card.component.html | 35 ++++- .../project-direction-card.component.scss | 47 ++++++ .../project-direction-card.component.ts | 23 ++- .../project-vacancy-card.component.html | 15 +- .../project-vacancy-card.component.scss | 10 +- .../project-vacancy-card.component.ts | 3 +- .../project-main-step.component.html | 14 +- ...ject-partner-resources-step.component.html | 6 +- .../project-team-step.component.html | 2 +- .../project-vacancy-step.component.html | 4 +- .../skills-basket.component.scss | 2 +- .../skills-group/skills-group.component.html | 4 +- .../skills-group/skills-group.component.scss | 10 +- .../vacancies/detail/info/info.component.html | 45 +++--- .../vacancies/detail/info/info.component.ts | 18 +++ .../delete-confirm.component.html | 14 +- .../delete-confirm.component.scss | 3 +- .../src/assets/icons/svg/left-arrow.svg | 2 +- .../assets/icons/symbol/svg/sprite.css.svg | 2 +- .../social_platform/src/styles/_colors.scss | 1 + .../primitives/back/back.component.scss | 4 +- 47 files changed, 425 insertions(+), 238 deletions(-) diff --git a/projects/core/src/consts/filters/rating-filter.const.ts b/projects/core/src/consts/filters/rating-filter.const.ts index 56da6d564..4c9ea0744 100644 --- a/projects/core/src/consts/filters/rating-filter.const.ts +++ b/projects/core/src/consts/filters/rating-filter.const.ts @@ -2,22 +2,22 @@ export const ratingFilters = [ { - label: "Месяц", + label: "месяц", id: 0, value: "last_month", }, { - label: "Год", + label: "год", id: 1, value: "last_year", }, { - label: "День", + label: "день", id: 2, value: "last_day", }, { - label: "Неделя", + label: "неделя", id: 3, value: "last_week", }, diff --git a/projects/core/src/consts/filters/work-experience-filter.const.ts b/projects/core/src/consts/filters/work-experience-filter.const.ts index d0739c1c3..8e0129d22 100644 --- a/projects/core/src/consts/filters/work-experience-filter.const.ts +++ b/projects/core/src/consts/filters/work-experience-filter.const.ts @@ -1,8 +1,8 @@ /** @format */ export const workExperienceFilter = [ - { label: "Без опыта", value: "no_experience" }, - { label: "До 1 года", value: "up_to_a_year" }, - { label: "От 1 года до 3 лет", value: "from_one_to_three_years" }, - { label: "От 3 лет и более", value: "from_three_years" }, + { label: "без опыта", value: "no_experience" }, + { label: "до 1 года", value: "up_to_a_year" }, + { label: "от 1 года до 3 лет", value: "from_one_to_three_years" }, + { label: "от 3 лет и более", value: "from_three_years" }, ]; diff --git a/projects/core/src/consts/filters/work-format-filter.const.ts b/projects/core/src/consts/filters/work-format-filter.const.ts index d50b3c32e..abcb6acb6 100644 --- a/projects/core/src/consts/filters/work-format-filter.const.ts +++ b/projects/core/src/consts/filters/work-format-filter.const.ts @@ -1,7 +1,7 @@ /** @format */ export const workFormatFilter = [ - { label: "Удаленная работа", value: "remote" }, - { label: "Работа в офисе", value: "office" }, - { label: "Смешанный формат", value: "hybrid" }, + { label: "удаленная работа", value: "remote" }, + { label: "работа в офисе", value: "office" }, + { label: "смешанный формат", value: "hybrid" }, ]; diff --git a/projects/core/src/consts/filters/work-schedule-filter.const.ts b/projects/core/src/consts/filters/work-schedule-filter.const.ts index f085cbedd..c59ec3e0a 100644 --- a/projects/core/src/consts/filters/work-schedule-filter.const.ts +++ b/projects/core/src/consts/filters/work-schedule-filter.const.ts @@ -1,9 +1,9 @@ /** @format */ export const workScheduleFilter = [ - { label: "Полный рабочий день", value: "full_time" }, - { label: "Сменный график", value: "shift_work" }, - { label: "Гибкий график", value: "flexible_schedule" }, - { label: "Частичная занятость", value: "part_time" }, - { label: "Стажировка", value: "internship" }, + { label: "полный рабочий день", value: "full_time" }, + { label: "сменный график", value: "shift_work" }, + { label: "гибкий график", value: "flexible_schedule" }, + { label: "частичная занятость", value: "part_time" }, + { label: "стажировка", value: "internship" }, ]; diff --git a/projects/core/src/consts/lists/education-info-list.const.ts b/projects/core/src/consts/lists/education-info-list.const.ts index f34197734..d9dc0806c 100644 --- a/projects/core/src/consts/lists/education-info-list.const.ts +++ b/projects/core/src/consts/lists/education-info-list.const.ts @@ -4,17 +4,17 @@ export const educationUserType = [ { id: 0, value: "Ученик", - label: "Ученик", + label: "ученик", }, { id: 1, value: "Студент", - label: "Студент", + label: "студент", }, { id: 2, value: "Выпускник", - label: "Выпускник", + label: "выпускник", }, ]; @@ -22,26 +22,26 @@ export const educationUserLevel = [ { id: 0, value: "Среднее общее образование", - label: "Среднее общее образование", + label: "среднее общее образование", }, { id: 1, value: "Среднее профессиональное образование", - label: "Среднее профессиональное образование", + label: "среднее профессиональное образование", }, { id: 2, value: "Высшее образование – бакалавриат, специалитет", - label: "Высшее образование – бакалавриат, специалитет", + label: "высшее образование – бакалавриат, специалитет", }, { id: 3, value: "Высшее образование – магистратура", - label: "Высшее образование – магистратура", + label: "высшее образование – магистратура", }, { id: 4, value: "Высшее образование – аспирантура", - label: "Высшее образование – аспирантура", + label: "высшее образование – аспирантура", }, ]; diff --git a/projects/core/src/consts/lists/language-info-list.const.ts b/projects/core/src/consts/lists/language-info-list.const.ts index a961335fe..ab176e59d 100644 --- a/projects/core/src/consts/lists/language-info-list.const.ts +++ b/projects/core/src/consts/lists/language-info-list.const.ts @@ -4,62 +4,62 @@ export const languageNamesList = [ { id: 0, value: "Английский", - label: "Английский", + label: "английский", }, { id: 1, value: "Испанский", - label: "Испанский", + label: "испанский", }, { id: 2, value: "Итальянский", - label: "Итальянский", + label: "итальянский", }, { id: 3, value: "Немецкий", - label: "Немецкий", + label: "немецкий", }, { id: 4, value: "Японский", - label: "Японский", + label: "японский", }, { id: 5, value: "Китайский", - label: "Китайский", + label: "китайский", }, { id: 6, value: "Арабский", - label: "Арабский", + label: "арабский", }, { id: 7, value: "Шведский", - label: "Шведский", + label: "шведский", }, { id: 8, value: "Польский", - label: "Польский", + label: "польский", }, { id: 9, value: "Чешский", - label: "Чешский", + label: "чешский", }, { id: 10, value: "Русский", - label: "Русский", + label: "русский", }, { id: 11, value: "Французский", - label: "Французский", + label: "французский", }, ]; diff --git a/projects/core/src/consts/lists/ldirection-project-list.const.ts b/projects/core/src/consts/lists/ldirection-project-list.const.ts index c542dd096..4363da648 100644 --- a/projects/core/src/consts/lists/ldirection-project-list.const.ts +++ b/projects/core/src/consts/lists/ldirection-project-list.const.ts @@ -8,7 +8,7 @@ export const directionProjectList = [ { id: 0, value: "Технология", // Значение для отправки на сервер - label: "Технология", // Отображаемый текст + label: "технология", // Отображаемый текст }, { id: 1, @@ -18,31 +18,31 @@ export const directionProjectList = [ { id: 2, value: "Транспорт", - label: "Транспорт", + label: "транспорт", }, { id: 3, value: "Хим Био", - label: "Хим Био", + label: "хим био", }, { id: 4, value: "Дизайн", - label: "Дизайн", + label: "дизайн", }, { id: 5, value: "Мультимедиа", - label: "Мультимедиа", + label: "мультимедиа", }, { id: 6, value: "СоцТех", - label: "СоцТех", + label: "соцтех", }, { id: 7, value: "Урбанистика", - label: "Урбанистика", + label: "урбанистика", }, ]; diff --git a/projects/core/src/consts/lists/resource-options-list.const.ts b/projects/core/src/consts/lists/resource-options-list.const.ts index 7b0e90206..83335eabb 100644 --- a/projects/core/src/consts/lists/resource-options-list.const.ts +++ b/projects/core/src/consts/lists/resource-options-list.const.ts @@ -4,21 +4,21 @@ export const resourceOptionsList = [ { id: 1, value: "infrastructure", - label: "Инфраструктурный", + label: "инфраструктурный", }, { id: 2, value: "staff", - label: "Кадровый", + label: "кадровый", }, { id: 3, value: "financial", - label: "Финансовый", + label: "финансовый", }, { id: 4, value: "information", - label: "Информационный", + label: "информационный", }, ]; diff --git a/projects/core/src/consts/lists/roles-members-list.const.ts b/projects/core/src/consts/lists/roles-members-list.const.ts index 58bff5306..cbf62aeb5 100644 --- a/projects/core/src/consts/lists/roles-members-list.const.ts +++ b/projects/core/src/consts/lists/roles-members-list.const.ts @@ -3,28 +3,28 @@ export const rolesMembersList = [ { id: 0, - value: "Наставник", - label: "Наставник", + value: "наставник", + label: "наставник", }, { id: 1, value: "Руководитель проекта", - label: "Руководитель проекта", + label: "руководитель проекта", }, { id: 2, value: "Руководитель направления", - label: "Руководитель направления", + label: "руководитель направления", }, { id: 3, value: "Проектный менеджер", - label: "Проектный менеджер", + label: "проектный менеджер", }, { id: 4, value: "Руководитель трека", - label: "Руководитель трека", + label: "руководитель трека", }, { id: 5, @@ -34,6 +34,6 @@ export const rolesMembersList = [ { id: 6, value: "Участник", - label: "Участник", + label: "участник", }, ]; diff --git a/projects/core/src/consts/lists/track-project-list.const.ts b/projects/core/src/consts/lists/track-project-list.const.ts index ec73b07e3..e03d85937 100644 --- a/projects/core/src/consts/lists/track-project-list.const.ts +++ b/projects/core/src/consts/lists/track-project-list.const.ts @@ -4,26 +4,26 @@ export const trackProjectList = [ { id: 0, value: "Технологическое лидерство", - label: "Технологическое лидерство", + label: "технологическое лидерство", }, { id: 1, value: "Индустриальные", - label: "Индустриальные", + label: "индустриальные", }, { id: 2, value: "Инициативные", - label: "Инициативные", + label: "инициативные", }, { id: 3, value: "Стратегические", - label: "Стратегические", + label: "стратегические", }, { id: 4, value: "Научные", - label: "Научные", + label: "научные", }, ]; diff --git a/projects/core/src/consts/lists/work-experience-list.const.ts b/projects/core/src/consts/lists/work-experience-list.const.ts index 6fbc0038c..83bea1075 100644 --- a/projects/core/src/consts/lists/work-experience-list.const.ts +++ b/projects/core/src/consts/lists/work-experience-list.const.ts @@ -4,21 +4,21 @@ export const workExperienceList = [ { id: 0, value: "без опыта", - label: "Без опыта", + label: "без опыта", }, { id: 1, value: "до 1 года", - label: "До 1 года", + label: "до 1 года", }, { id: 2, value: "от 1 года до 3 лет", - label: "От 1 года до 3 лет", + label: "от 1 года до 3 лет", }, { id: 3, value: "от 3 лет и более", - label: "От 3 лет и более", + label: "от 3 лет и более", }, ]; diff --git a/projects/core/src/consts/lists/work-format-list.const.ts b/projects/core/src/consts/lists/work-format-list.const.ts index 663eeb688..3b9a58d5c 100644 --- a/projects/core/src/consts/lists/work-format-list.const.ts +++ b/projects/core/src/consts/lists/work-format-list.const.ts @@ -4,16 +4,16 @@ export const workFormatList = [ { id: 0, value: "удаленная работа", - label: "Удаленная работа", + label: "удаленная работа", }, { id: 1, value: "работа в офисе", - label: "Работа в офисе", + label: "работа в офисе", }, { id: 2, value: "смешанная", - label: "Смешанная", + label: "смешанная", }, ]; diff --git a/projects/core/src/consts/lists/work-schelude-list.const.ts b/projects/core/src/consts/lists/work-schelude-list.const.ts index df6f33a95..729368d61 100644 --- a/projects/core/src/consts/lists/work-schelude-list.const.ts +++ b/projects/core/src/consts/lists/work-schelude-list.const.ts @@ -4,26 +4,26 @@ export const workScheludeList = [ { id: 0, value: "полный рабочий день", - label: "Полный рабочий день", + label: "полный рабочий день", }, { id: 1, value: "сменный график", - label: "Сменный график", + label: "сменный график", }, { id: 2, value: "гибкий график", - label: "Гибкий график", + label: "гибкий график", }, { id: 3, value: "частичная занятость", - label: "Частичная занятость", + label: "частичная занятость", }, { id: 4, value: "стажировка", - label: "Стажировка", + label: "стажировка", }, ]; 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 9e308f3ad..08d0ba6d8 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 @@ -1,9 +1,12 @@ -
    +
    + @if ((listType !== 'profile' || !isProjectChatPage) && !(listType === 'project' && + isProjectChatPage)) {
    + }
    @if (info()) { @@ -571,11 +574,17 @@

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

    Твое CV уже ждет тебя на почте :)

    + +

    твое CV уже ждет тебя на почте :)

    -

    - Кстати, оно часто залетает в папку «Спам» — обязательно проверь и там тоже.
    +

    + кстати, оно часто залетает в папку «Спам» — обязательно проверь и там тоже.
    Технические сложности? Мы всегда на связи в Telegram — {{ "@procollab_support" }}

    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 86288168f..5b3345036 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 @@ -72,6 +72,7 @@ $detail-bar-mb: 12px; cursor: pointer; background-color: var(--white); border-radius: 50%; + z-index: 100; &--program { bottom: 15px; 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 28fb75f2b..271620627 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 @@ -442,8 +442,6 @@ export class DeatilComponent implements OnInit, OnDestroy { this.backPath = "/office/projects/all"; } else if (this.listType === "program") { this.backPath = "/office/program/all"; - } else { - this.backPath = "/office/members"; } } } diff --git a/projects/social_platform/src/app/office/feed/filter/feed-filter.component.scss b/projects/social_platform/src/app/office/feed/filter/feed-filter.component.scss index 335b07c0e..2928910f7 100644 --- a/projects/social_platform/src/app/office/feed/filter/feed-filter.component.scss +++ b/projects/social_platform/src/app/office/feed/filter/feed-filter.component.scss @@ -86,6 +86,7 @@ align-items: center; justify-content: center; height: 66px; + width: 156px; padding: 40px 24px 12px; cursor: pointer; background-color: var(--light-white); diff --git a/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.html b/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.html index ebded859f..066187810 100644 --- a/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.html +++ b/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.html @@ -19,15 +19,12 @@

    {{ project.name }}

    - @if (industryService.industries | async; as industries) { -

    - @if (industryService.getIndustry(industries, project.industry); as industry) { - - {{ industry.name }} - - } -

    - } + @if (industryService.industries | async; as industries) { @if + (industryService.getIndustry(industries, project.industry); as industry) { + + {{ industry.name }} + + } }

    } @if (feedItem; as vacancy) {
    diff --git a/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.scss b/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.scss index e0c696fc7..57ae20c5f 100644 --- a/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.scss +++ b/projects/social_platform/src/app/office/feed/shared/open-vacancy/open-vacancy.component.scss @@ -85,7 +85,7 @@ ::ng-deep { app-button { .button--big { - padding: 4px 19px; + padding: 4px 16px; } } } diff --git a/projects/social_platform/src/app/office/models/project.model.ts b/projects/social_platform/src/app/office/models/project.model.ts index 42417d312..eead36f68 100644 --- a/projects/social_platform/src/app/office/models/project.model.ts +++ b/projects/social_platform/src/app/office/models/project.model.ts @@ -1,7 +1,10 @@ /** @format */ import { Collaborator } from "./collaborator.model"; +import { Goal } from "./goals.model"; import { PartnerProgramFields, PartnerProgramFieldsValues } from "./partner-program-fields.model"; +import { Partner } from "./partner.model"; +import { Resource } from "./resource.model"; import { Vacancy } from "./vacancy.model"; /** @@ -38,6 +41,9 @@ export class Project { region!: string; shortDescription!: string; achievements!: { id: number; title: string; status: string }[]; + partners!: Partner[]; + resources!: Resource[]; + goals!: Goal[]; industry!: number; presentationAddress!: string; imageAddress!: string; @@ -71,6 +77,9 @@ export class Project { description: "string", shortDescription: "string", achievements: [{ id: 3, title: "sdf", status: "dsaf" }], + partners: [], + resources: [], + goals: [], industry: 0, viewsCount: 0, links: [], diff --git a/projects/social_platform/src/app/office/models/resource.model.ts b/projects/social_platform/src/app/office/models/resource.model.ts index b1a193fab..ca4a5542c 100644 --- a/projects/social_platform/src/app/office/models/resource.model.ts +++ b/projects/social_platform/src/app/office/models/resource.model.ts @@ -3,7 +3,7 @@ export interface Resource { id: number; projectId: number; - type: string; + type: "infrastructure" | "staff" | "financial" | "information"; description: string; partnerCompany: number; } diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.html b/projects/social_platform/src/app/office/profile/edit/edit.component.html index 3482c1639..4a2bf66fe 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.html +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.html @@ -156,7 +156,7 @@

    редактирование профиля

    id="lastName" type="text" formControlName="lastName" - placeholder="Иванов" + placeholder="иванов" > @if (lastName | controlError: "required") {
    @@ -176,7 +176,7 @@

    редактирование профиля

    id="city" [error]="city | controlError" type="text" - placeholder="Москва" + placeholder="москва" formControlName="city" > @if (city | controlError: "required") { @@ -214,7 +214,7 @@

    редактирование профиля

    size="big" id="userType" formControlName="userType" - placeholder="Эксперт" + placeholder="эксперт" [options]="options" > } @if (userType | controlError: "required") { @@ -237,7 +237,7 @@

    редактирование профиля

    [forceSelect]="true" [error]="speciality | controlError" (searchStart)="onSearchSpec($event)" - placeholder="Java Developer" + placeholder="java developer" formControlName="speciality" (openSkillsFunc)="toggleSpecsGroupsModal()" > @@ -261,7 +261,7 @@

    редактирование профиля

    id="aboutMe" [error]="aboutMe | controlError" type="text" - placeholder="Я java developer, очень люблю котиков" + placeholder="я java developer, очень люблю котиков" formControlName="aboutMe" > @if (aboutMe | controlError: "required") { @@ -278,7 +278,12 @@

    редактирование профиля

    -
      +
        @for (control of links.controls; let i = $index; track i) {
      • редактирование профиля @if (organizationName | controlError: "required") { @@ -403,7 +408,7 @@

        редактирование профиля

        @if (description | controlError: "required") { @@ -423,7 +428,7 @@

        редактирование профиля

        size="big" [selectedId]="selectedEducationLevelId()" formControlName="educationLevel" - placeholder="Высшее образование-магистратура" + placeholder="высшее образование-магистратура" [options]="educationLevelList" > @@ -445,7 +450,7 @@

        редактирование профиля

        size="big" [selectedId]="selectedEducationStatusId()" formControlName="educationStatus" - placeholder="Студент" + placeholder="студент" [options]="educationStatusList" > @@ -472,8 +477,8 @@

        редактирование профиля

        class="profile__add-education" (click)="addEducation()" > - добавить образование - + {{ editEducationClick ? "сохранить изменения" : "добавить образование" }} +
    @@ -509,12 +514,12 @@

    редактирование профиля

    -
    - +
    +
    -
    - +
    +
    @@ -579,7 +584,7 @@

    редактирование профиля

    id="organization" [error]="organization | controlError" type="text" - placeholder="Проколлаб" + placeholder="проколлаб" formControlName="organization" > @if (organization | controlError: "required") { @@ -600,7 +605,7 @@

    редактирование профиля

    id="jobPosition" [error]="jobPosition | controlError" type="text" - placeholder="Дизайнер" + placeholder="дизайнер" formControlName="jobPosition" > @if (jobPosition | controlError: "required") { @@ -621,7 +626,7 @@

    редактирование профиля

    id="descriptionWork" [error]="descriptionWork | controlError" type="text" - placeholder="Проект начал свое существование с 2020 года" + placeholder="проект начал свое существование с 2020 года" formControlName="descriptionWork" > @if (descriptionWork | controlError: "required") { @@ -646,8 +651,8 @@

    редактирование профиля

    class="profile__add-education" (click)="addWork()" > - добавить работу - + {{ editWorkClick ? "сохранить изменения" : "добавить работу" }} +
    @@ -679,18 +684,12 @@

    редактирование профиля

    -
    - +
    +
    -
    - +
    +
    @@ -796,8 +795,8 @@

    редактирование профиля

    }" (click)="addAchievement()" > - добавить достижение - + {{ editAchievementsClick ? "сохранить изменения" : "добавить достижение" }} +
    @@ -819,8 +818,15 @@

    редактирование профиля

    {{ achievementItem.status }}

    - @if (achievementItem.files?.length) { @for (file of achievementItem.files; track - $index) { + @if (achievementItem.files?.length) { @if (isStringFiles(achievementItem.files)) { + + + } @else { @for (file of achievementItem.files; track $index) { редактирование профиля [link]="file.link" > - } } + } } }
    -
    - +
    +
    -
    - +
    +
    @@ -855,11 +855,11 @@

    редактирование профиля

    - +
    редактирование профиля size="big" [selectedId]="selectedLanguageId()" formControlName="language" - placeholder="Английский" + placeholder="английский" [options]="languageList" > @@ -927,18 +927,19 @@

    редактирование профиля

    }
    - - количество добавляемых языков не более 4-х } + количество добавляемых языков не более 4-х редактирование профиля }" (click)="addLanguage()" > - добавить язык - + {{ editLanguageClick ? "сохранить изменения" : "добавить язык" }} +
    @@ -960,12 +961,12 @@

    редактирование профиля

    -
    - +
    +
    -
    - +
    +
    @@ -1003,15 +1004,16 @@

    редактирование профиля

    -

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

    +

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

    @if (isModalErrorSkillChooseText()) { -

    {{ isModalErrorSkillChooseText() }}.

    +

    {{ isModalErrorSkillChooseText() }}.

    } @else { -

    +

    для публикации профиля, нужно заполнить все обязательные поля (они будут подсвечены красным). @@ -1058,15 +1060,18 @@

    более специальностей

    - Сохранитьсохранить
    diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.scss b/projects/social_platform/src/app/office/profile/edit/edit.component.scss index f25f86be2..6431da64e 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.scss +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.scss @@ -106,6 +106,13 @@ flex-grow: 1; min-width: 0; max-width: 333px; + + ::ng-deep { + app-upload-file { + height: 80px; + padding: 10px 30px; + } + } } &__slides-title { diff --git a/projects/social_platform/src/app/office/profile/edit/edit.component.ts b/projects/social_platform/src/app/office/profile/edit/edit.component.ts index 98be6adf1..6418a8888 100644 --- a/projects/social_platform/src/app/office/profile/edit/edit.component.ts +++ b/projects/social_platform/src/app/office/profile/edit/edit.component.ts @@ -352,6 +352,16 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { skillsGroupsModalOpen = signal(false); + openGroupIndex: number | null = null; + + onGroupToggled(index: number, isOpen: boolean) { + this.openGroupIndex = isOpen ? index : null; + } + + isGroupDisabled(index: number): boolean { + return this.openGroupIndex !== null && this.openGroupIndex !== index; + } + educationItems = signal([]); workItems = signal([]); @@ -1054,6 +1064,13 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { this.isModalErrorSkillChooseText.set(error.error.phone_number[0]); } else if (error.error.language) { this.isModalErrorSkillChooseText.set(error.error.language); + } else if (error.error.achievements) { + this.isModalErrorSkillChooseText.set(error.error.achievements[0]); + } else if (error.error.work_experience[2]) { + const errorText = error.error.work_experience[2].entry_year + ? error.error.work_experience[2].entry_year + : error.error.work_experience[2].completion_year; + this.isModalErrorSkillChooseText.set(errorText); } else { this.isModalErrorSkillChooseText.set(error.error[0]); } @@ -1148,4 +1165,8 @@ export class ProfileEditComponent implements OnInit, OnDestroy, AfterViewInit { toggleSpecsGroupsModal(): void { this.specsGroupsModalOpen.update(open => !open); } + + isStringFiles(files: any[]): boolean { + return typeof files === "string"; + } } diff --git a/projects/social_platform/src/app/office/projects/detail/info/info.component.html b/projects/social_platform/src/app/office/projects/detail/info/info.component.html index 6b3661cc8..ff152261b 100644 --- a/projects/social_platform/src/app/office/projects/detail/info/info.component.html +++ b/projects/social_platform/src/app/office/projects/detail/info/info.component.html @@ -88,6 +88,7 @@

    о проекте

    [icon]="directionItem.icon" [about]="directionItem.about" [type]="directionItem.type" + [projectInfoType]="directionItem.direction === 'цели' ? 'goals' : 'partners'" > }
    @@ -130,37 +131,39 @@

    контакты

    }
    - } - - - @if (project.links.length) { + } @if (project.resources.length) {

    в поиске

    + @if (project.resources; as resources) {
      + @for (resourceItem of resources; track $index) { @switch (resourceItem.type) { @case + ("infrastructure") {
    • - -

      2 ляма

      + +

      {{ resourceItem.description }}

    • - + } @case ("staff") {
    • -

      расширение нннадо

      +

      {{ resourceItem.description }}

    • - + } @case ("financial") {
    • - -

      аренда сервера

      + +

      {{ resourceItem.description }}

    • - + } @case ("information") {
    • -

      публикация в СМИ

      +

      {{ resourceItem.description }}

    • + } } }
    + }
    } @if (project.achievements.length) {
    diff --git a/projects/social_platform/src/app/office/projects/detail/info/info.component.ts b/projects/social_platform/src/app/office/projects/detail/info/info.component.ts index 739e7bf8a..760f7ca23 100644 --- a/projects/social_platform/src/app/office/projects/detail/info/info.component.ts +++ b/projects/social_platform/src/app/office/projects/detail/info/info.component.ts @@ -131,8 +131,8 @@ export class ProjectInfoComponent implements OnInit, AfterViewInit, OnDestroy { this.project?.problem, this.project?.targetAudience, this.project?.actuality, - "", - "", + this.project?.goals, + this.project.partners, ], ["string", "string", "string", "array", "array"] )!; diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.html b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.html index bd359441c..b263a3c38 100644 --- a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.html +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.html @@ -14,7 +14,7 @@ @if (type === 'string') {

    {{ about }}

    - } @else { @if (profileInfoType === "skills") { + } @else { @if (listType === 'profile') { @if (profileInfoType === "skills") {
      @for (aboutItem of about; track $index) { @@ -73,6 +73,37 @@
    } - } } + } } @else { @if (projectInfoType === 'goals') { +
    + @if (this.about.length > 0) { @for (goalItem of about; track $index) { +
  • + +

    {{ goalItem.title }}

    +

    + {{ goalItem.completionDate | dayjs: "format":"DD.MM.YY" }} +

    +
  • + } } +
    + } @else { +
    + @if (this.about.length > 0) { @for (partnerItem of about; track $index) { +
  • +
    +

    {{ partnerItem.company.name }}

    +

    {{ partnerItem.company.inn }}

    +
    + +

    + {{ + partnerItem.contribution.length < 484 + ? partnerItem.contribution + : partnerItem.contribution.slice(0, 480) + " " + "..." + }} +

    +
  • + } } +
    + } } }
    diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss index d1f09c316..27d9a6cb7 100644 --- a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss @@ -136,6 +136,53 @@ border-radius: var(--rounded-lg); } } + + &__goal { + display: flex; + align-items: center; + justify-content: space-between; + } +} + +.lists { + &__section { + display: flex; + justify-content: space-between; + margin-bottom: 8px; + border-bottom: 0.5px solid var(--accent); + } + + &__item { + display: flex; + gap: 6px; + align-items: center; + + &--status { + padding: 8px; + border: 0.5px solid var(--medium-grey-for-outline); + border-radius: var(--rounded-xl); + } + + &--title { + color: var(--black) !important; + + &--patner { + color: var(--green-dark); + } + } + + p { + color: var(--accent); + } + } + + &__title { + margin-bottom: 8px; + + &--partner { + color: var(--green-dark); + } + } } .skills { diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.ts b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.ts index e87620f54..a4f5d5ea6 100644 --- a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.ts +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.ts @@ -11,11 +11,21 @@ import { map, Subscription, switchMap } from "rxjs"; import { Achievement } from "@auth/models/user.model"; import { FileItemComponent } from "@ui/components/file-item/file-item.component"; import { FileModel } from "@office/models/file.model"; +import { AvatarComponent } from "@ui/components/avatar/avatar.component"; +import { DayjsPipe } from "@corelib"; @Component({ selector: "app-project-direction-card", templateUrl: "./project-direction-card.component.html", styleUrl: "./project-direction-card.component.scss", - imports: [CommonModule, IconComponent, ModalComponent, TagComponent, FileItemComponent], + imports: [ + CommonModule, + IconComponent, + ModalComponent, + TagComponent, + FileItemComponent, + AvatarComponent, + DayjsPipe, + ], standalone: true, }) export class ProjectDirectionCard implements OnInit, OnDestroy { @@ -24,7 +34,8 @@ export class ProjectDirectionCard implements OnInit, OnDestroy { @Input() about!: string | any[]; @Input() type!: string; - @Input() profileInfoType?: "skills" | "achievements" = "skills"; + @Input() profileInfoType?: "skills" | "achievements"; + @Input() projectInfoType?: "goals" | "partners"; private readonly route = inject(ActivatedRoute); private readonly router = inject(Router); @@ -34,13 +45,19 @@ export class ProjectDirectionCard implements OnInit, OnDestroy { isShowModal = false; isOpenInfo = false; + listType?: "profile" | "project"; + // Поля для работы с достижениями achievements: Pick[] = []; files: FileModel[] = []; achievementsInfo = signal([]); currentYear = 0; ngOnInit(): void { + const listTypeSub$ = this.route.data.subscribe(data => { + this.listType = data["listType"]; + }); + if (this.profileInfoType === "achievements") { if (Array.isArray(this.about)) { this.about = Array.from( @@ -49,6 +66,8 @@ export class ProjectDirectionCard implements OnInit, OnDestroy { } this.getAchievementsByYear(); } + + this.subscriptions.push(listTypeSub$); } ngOnDestroy(): void { diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component.html b/projects/social_platform/src/app/office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component.html index 7f83d02d8..a1be97247 100644 --- a/projects/social_platform/src/app/office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component.html +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component.html @@ -11,8 +11,10 @@ borderColor="accent" >
    -

    {{ vacancy.project.name }}

    -

    19.08.25 20:15

    +

    {{ vacancy.project.name }}

    +

    + {{ vacancy.datetimeCreated | dayjs: "format":"DD.MM.YY • HH:MM" }} +

    } @else { @@ -24,7 +26,9 @@ }
    -

    {{ vacancy.salary ?? "по договоренности" }}

    +

    + {{ +vacancy.salary === 0 ? "по договоренности" : vacancy.salary }} +

    @@ -59,7 +63,10 @@ подробнее - откликнуться
    diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component.scss b/projects/social_platform/src/app/office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component.scss index e4a2bb69f..3c7ceeec5 100644 --- a/projects/social_platform/src/app/office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component.scss +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component.scss @@ -10,6 +10,10 @@ padding-bottom: 8px; border-bottom: 0.5px solid var(--accent); + p { + color: var(--grey-for-text); + } + &--top { display: flex; flex-direction: column; @@ -22,12 +26,6 @@ display: flex; gap: 10px; align-items: center; - - &--info { - p { - color: var(--black); - } - } } &__role { diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component.ts b/projects/social_platform/src/app/office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component.ts index c7fca5b2b..104ec1a80 100644 --- a/projects/social_platform/src/app/office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component.ts +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-vacancy-card/project-vacancy-card.component.ts @@ -5,7 +5,7 @@ import { RouterLink } from "@angular/router"; import { Vacancy } from "@office/models/vacancy.model"; import { IconComponent } from "@uilib"; import { ButtonComponent } from "@ui/components"; -import { ParseBreaksPipe, ParseLinksPipe } from "@corelib"; +import { DayjsPipe, ParseBreaksPipe, ParseLinksPipe } from "@corelib"; import { expandElement } from "@utils/expand-element"; import { TagComponent } from "@ui/components/tag/tag.component"; import { AvatarComponent } from "@ui/components/avatar/avatar.component"; @@ -40,6 +40,7 @@ import { AvatarComponent } from "@ui/components/avatar/avatar.component"; ParseBreaksPipe, TagComponent, AvatarComponent, + DayjsPipe, ], templateUrl: "./project-vacancy-card.component.html", styleUrl: "./project-vacancy-card.component.scss", 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 8377e1d02..44712ded7 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 @@ -98,7 +98,7 @@ id="name" formControlName="name" [error]="(name | controlError) || projSubmitInitiated" - placeholder="Название" + placeholder="название" > @if ((name | controlError: "required") || projSubmitInitiated) {
    @@ -114,7 +114,7 @@ id="region" formControlName="region" [error]="(region | controlError) || projSubmitInitiated" - placeholder="Введите регион" + placeholder="введите регион" > @if ((region | controlError: "required") || projSubmitInitiated) {
    @@ -130,7 +130,7 @@ size="big" id="industry" formControlName="industryId" - placeholder="Выберите сферу к которой относится ваш проект" + placeholder="выберите сферу к которой относится ваш проект" [options]="industries" > } @if (industry | controlError: "required") { @@ -179,7 +179,7 @@ id="problem" formControlName="problem" [error]="(problem | controlError) || projSubmitInitiated" - placeholder="Улучшить качество жизни студентов" + placeholder="улучшить качество жизни студентов" > @if ((problem | controlError: "required") || projSubmitInitiated) {
    @@ -201,7 +201,7 @@ [tooltipWidth]="350" [error]="(description | controlError) || projSubmitInitiated" formControlName="description" - placeholder="Проект начал свое существование с 2020 года" + placeholder="проект начал свое существование с 2020 года" > @if ((description | controlError: "required") || projSubmitInitiated) {
    @@ -226,7 +226,7 @@ [tooltipWidth]="350" formControlName="actuality" [error]="actuality | controlError" - placeholder="Указом президента от 03.07.2023 " + placeholder="указом президента от 03.07.2023 " > @if ((actuality | controlError: "required")) {
    @@ -278,7 +278,7 @@ @if (goalName | controlError: "required") { diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.html b/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.html index f249d6f42..535494817 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.html +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.html @@ -114,7 +114,7 @@ id="type" [options]="resourcesTypeOptions" size="big" - placeholder="Выбери вариант" + placeholder="выбери вариант" formControlName="type" > @if (type | controlError: "required") { @@ -129,7 +129,7 @@ @if (description | controlError: "required") { @@ -145,7 +145,7 @@ id="partnerCompany" [options]="resourcesCompanyOptions" size="big" - placeholder="Выбери вариант" + placeholder="выбери вариант" formControlName="partnerCompany" > @if (partnerCompany | controlError: "required") { diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.html b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.html index 514f5956b..3bcaefbc5 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.html +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-team-step/project-team-step.component.html @@ -60,7 +60,7 @@ [tooltipWidth]="300" formControlName="role" [error]="(role | controlError) && inviteSubmitInitiated()" - placeholder="Напишите роль участника в команде" + placeholder="напишите роль участника в команде" > @if ((role | controlError: "required") && inviteSubmitInitiated()) {
    diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.html b/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.html index daebd52e7..e5f5b057c 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.html +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-vacancy-step/project-vacancy-step.component.html @@ -12,7 +12,7 @@ id="vacancy_role" formControlName="role" [error]="(role | controlError) && vacancySubmitInitiated()" - placeholder="Напишите роль, которую вы хотите закрыть в вашем проекте" + placeholder="напишите роль, которую вы хотите закрыть в вашем проекте" > @if ((role | controlError: "required") && vacancySubmitInitiated()) {
    @@ -129,7 +129,7 @@
    {{ title }} + @if (!disabled) { + }
    @if (contentVisible()) {
    diff --git a/projects/social_platform/src/app/office/shared/skills-group/skills-group.component.scss b/projects/social_platform/src/app/office/shared/skills-group/skills-group.component.scss index edadc6235..299e96ef4 100644 --- a/projects/social_platform/src/app/office/shared/skills-group/skills-group.component.scss +++ b/projects/social_platform/src/app/office/shared/skills-group/skills-group.component.scss @@ -25,10 +25,10 @@ } } - &--disabled { - pointer-events: none; - cursor: not-allowed; + &--disabled { opacity: 0.5; + cursor: not-allowed; + pointer-events: none; } &__top { @@ -36,8 +36,10 @@ align-items: center; justify-content: space-between; width: 100%; + transition: width 0.2s ease; - &--open { + .heading--open &, + &--has-open { width: 50%; } diff --git a/projects/social_platform/src/app/office/vacancies/detail/info/info.component.html b/projects/social_platform/src/app/office/vacancies/detail/info/info.component.html index 3428c158c..6a0d60ce0 100644 --- a/projects/social_platform/src/app/office/vacancies/detail/info/info.component.html +++ b/projects/social_platform/src/app/office/vacancies/detail/info/info.component.html @@ -65,9 +65,11 @@

    {{ project.name }}

    @@ -132,34 +134,33 @@

    метаданные

    - - +
      + @for (link of project.links; track $index) { +
    • + @if (link | userLinks; as l) { + + + {{ l.tag }} + + } +
    • + } +
    +
    + }
    }
    - +

    отклик на вакансию

    diff --git a/projects/social_platform/src/app/office/vacancies/detail/info/info.component.ts b/projects/social_platform/src/app/office/vacancies/detail/info/info.component.ts index fdd1145ca..f31476b7d 100644 --- a/projects/social_platform/src/app/office/vacancies/detail/info/info.component.ts +++ b/projects/social_platform/src/app/office/vacancies/detail/info/info.component.ts @@ -97,6 +97,7 @@ import { TextareaComponent } from "@ui/components/textarea/textarea.component"; }) export class VacancyInfoComponent implements OnInit { private readonly route = inject(ActivatedRoute); + private readonly router = inject(Router); private readonly vacancyService = inject(VacancyService); private readonly validationService = inject(ValidationService); private readonly cdRef = inject(ChangeDetectorRef); @@ -140,6 +141,14 @@ export class VacancyInfoComponent implements OnInit { this.route.data.pipe(map(r => r["data"])).subscribe((vacancy: Vacancy) => { this.vacancy = vacancy; }); + + this.route.queryParams.subscribe({ + next: r => { + if (r["sendResponse"]) { + this.openModal.set(true); + } + }, + }); } ngAfterViewInit(): void { @@ -156,6 +165,15 @@ export class VacancyInfoComponent implements OnInit { this.subscriptions$.forEach($ => $.unsubscribe()); } + closeSendResponseModal(): void { + this.openModal.set(false); + + this.router.navigate([], { + queryParams: {}, + replaceUrl: true, + }); + } + /** * Обработчик отправки формы * Валидирует форму и отправляет отклик на сервер diff --git a/projects/social_platform/src/app/ui/components/delete-confirm/delete-confirm.component.html b/projects/social_platform/src/app/ui/components/delete-confirm/delete-confirm.component.html index 0aad1e9a6..4059b92b4 100644 --- a/projects/social_platform/src/app/ui/components/delete-confirm/delete-confirm.component.html +++ b/projects/social_platform/src/app/ui/components/delete-confirm/delete-confirm.component.html @@ -3,13 +3,15 @@ @if (settings$ | async; as settings) {
    - -

    {{ settings.mainText }}

    -

    {{ settings.subText }}

    - - Отменить + +

    {{ settings.mainText }}

    +

    {{ settings.subText }}

    + + отменить - Удалить + удалить
    }
    diff --git a/projects/social_platform/src/app/ui/components/delete-confirm/delete-confirm.component.scss b/projects/social_platform/src/app/ui/components/delete-confirm/delete-confirm.component.scss index a46414da4..9ee2774fb 100644 --- a/projects/social_platform/src/app/ui/components/delete-confirm/delete-confirm.component.scss +++ b/projects/social_platform/src/app/ui/components/delete-confirm/delete-confirm.component.scss @@ -4,7 +4,8 @@ display: flex; flex-direction: column; align-items: center; - max-width: 310px; + justify-content: center; + max-width: 420px; &__icon { margin-bottom: 18px; diff --git a/projects/social_platform/src/assets/icons/svg/left-arrow.svg b/projects/social_platform/src/assets/icons/svg/left-arrow.svg index 89f75442b..8fc70f937 100644 --- a/projects/social_platform/src/assets/icons/svg/left-arrow.svg +++ b/projects/social_platform/src/assets/icons/svg/left-arrow.svg @@ -1,3 +1,3 @@ - + diff --git a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg index 1937c5c01..0d291c462 100644 --- a/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg +++ b/projects/social_platform/src/assets/icons/symbol/svg/sprite.css.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/projects/social_platform/src/styles/_colors.scss b/projects/social_platform/src/styles/_colors.scss index 49444a3ab..9deab347f 100644 --- a/projects/social_platform/src/styles/_colors.scss +++ b/projects/social_platform/src/styles/_colors.scss @@ -28,6 +28,7 @@ // FUNCTIONAL --green: #88c9a1; + --green-dark: #297373; --red: #d48a9e; --red-dark: #{color.adjust(#d48a9e, $blackness: 10%)}; } diff --git a/projects/ui/src/lib/components/primitives/back/back.component.scss b/projects/ui/src/lib/components/primitives/back/back.component.scss index bfba87dc1..72b38d4d6 100644 --- a/projects/ui/src/lib/components/primitives/back/back.component.scss +++ b/projects/ui/src/lib/components/primitives/back/back.component.scss @@ -4,10 +4,10 @@ display: flex; align-items: center; margin-bottom: 45px; - color: var(--black); - cursor: pointer; &__icon { + cursor: pointer; + color: var(--grey-for-text); flex-shrink: 0; } From 47ec48862c3ba7757f866bf5aa70595f1f6e12ad Mon Sep 17 00:00:00 2001 From: Awakich Date: Wed, 29 Oct 2025 16:36:32 +0300 Subject: [PATCH 117/126] fix styles for feed-filter, direction-card, skills-group modal, detail components --- .../src/app/office/features/detail/detail.component.scss | 2 +- .../src/app/office/feed/filter/feed-filter.component.scss | 2 +- .../project-direction-card.component.scss | 6 +++--- .../office/shared/skills-group/skills-group.component.scss | 6 +++--- .../src/lib/components/primitives/back/back.component.scss | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) 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 5b3345036..e0b8f7204 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 @@ -68,11 +68,11 @@ $detail-bar-mb: 12px; position: absolute; bottom: -10px; left: 50%; + z-index: 100; display: block; cursor: pointer; background-color: var(--white); border-radius: 50%; - z-index: 100; &--program { bottom: 15px; diff --git a/projects/social_platform/src/app/office/feed/filter/feed-filter.component.scss b/projects/social_platform/src/app/office/feed/filter/feed-filter.component.scss index 2928910f7..640a68b85 100644 --- a/projects/social_platform/src/app/office/feed/filter/feed-filter.component.scss +++ b/projects/social_platform/src/app/office/feed/filter/feed-filter.component.scss @@ -85,8 +85,8 @@ flex-direction: column; align-items: center; justify-content: center; - height: 66px; width: 156px; + height: 66px; padding: 40px 24px 12px; cursor: pointer; background-color: var(--light-white); diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss index 27d9a6cb7..1f4ca1a50 100644 --- a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss @@ -145,13 +145,13 @@ } .lists { - &__section { + &__section { display: flex; justify-content: space-between; margin-bottom: 8px; border-bottom: 0.5px solid var(--accent); } - + &__item { display: flex; gap: 6px; @@ -176,7 +176,7 @@ } } - &__title { + &__title { margin-bottom: 8px; &--partner { diff --git a/projects/social_platform/src/app/office/shared/skills-group/skills-group.component.scss b/projects/social_platform/src/app/office/shared/skills-group/skills-group.component.scss index 299e96ef4..e7031401f 100644 --- a/projects/social_platform/src/app/office/shared/skills-group/skills-group.component.scss +++ b/projects/social_platform/src/app/office/shared/skills-group/skills-group.component.scss @@ -25,10 +25,10 @@ } } - &--disabled { - opacity: 0.5; - cursor: not-allowed; + &--disabled { pointer-events: none; + cursor: not-allowed; + opacity: 0.5; } &__top { diff --git a/projects/ui/src/lib/components/primitives/back/back.component.scss b/projects/ui/src/lib/components/primitives/back/back.component.scss index 72b38d4d6..9613e0f95 100644 --- a/projects/ui/src/lib/components/primitives/back/back.component.scss +++ b/projects/ui/src/lib/components/primitives/back/back.component.scss @@ -6,9 +6,9 @@ margin-bottom: 45px; &__icon { - cursor: pointer; - color: var(--grey-for-text); flex-shrink: 0; + color: var(--grey-for-text); + cursor: pointer; } span { From b3e1b85d340c31e406f3a75d8e372e994a1bda0a Mon Sep 17 00:00:00 2001 From: Awakich Date: Thu, 30 Oct 2025 13:21:14 +0300 Subject: [PATCH 118/126] fix feed duplicated news, profile logo program & name in news, add completing modal for goals --- .../features/detail/detail.component.html | 13 +- .../features/detail/detail.component.scss | 5 +- .../info-card/info-card.component.html | 14 +- .../features/info-card/info-card.component.ts | 19 +- .../news-card/news-card.component.html | 4 +- .../src/app/office/feed/feed.component.html | 2 +- .../src/app/office/feed/feed.component.ts | 220 +++++------------- .../profile/detail/main/main.component.html | 2 +- .../profile/detail/main/main.component.scss | 5 +- .../program/detail/list/list.component.html | 8 +- .../program/detail/list/list.component.scss | 2 +- .../program/detail/list/list.component.ts | 2 + .../program/detail/main/main.component.html | 2 +- .../projects/detail/info/info.component.html | 1 + .../project-direction-card.component.html | 34 ++- .../project-direction-card.component.scss | 42 +++- .../project-direction-card.component.ts | 77 ++++++ .../project-main-step.component.html | 11 +- .../office/projects/projects.component.html | 5 +- .../app/office/services/project.service.ts | 2 +- .../office/vacancies/list/list.component.scss | 5 +- .../ui/components/input/input.component.scss | 2 +- .../primitives/back/back.component.scss | 2 +- 23 files changed, 260 insertions(+), 219 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 08d0ba6d8..2297d607f 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 @@ -1,13 +1,6 @@ -
    - @if ((listType !== 'profile' || !isProjectChatPage) && !(listType === 'project' && - isProjectChatPage)) { -
    - -
    - } - +
    @if (info()) {
    @@ -81,7 +74,7 @@ зарегистрироваться - } } @else if (isUserMember) { + } } @else if (isUserMember && isUserManager) { подать проект - } @else if (isUserManager) { + } @else if (isUserManager && !isUserMember) {
    @if (type === 'projects') { - + проект } @else if (type === 'members') { - + профиль } @else { diff --git a/projects/social_platform/src/app/office/features/info-card/info-card.component.ts b/projects/social_platform/src/app/office/features/info-card/info-card.component.ts index 62314c118..4ddfeda20 100644 --- a/projects/social_platform/src/app/office/features/info-card/info-card.component.ts +++ b/projects/social_platform/src/app/office/features/info-card/info-card.component.ts @@ -1,7 +1,6 @@ /** @format */ import { Component, EventEmitter, inject, Input, OnInit, Output } from "@angular/core"; -import { Project } from "@models/project.model"; import { IndustryService } from "@services/industry.service"; import { IconComponent, ButtonComponent } from "@ui/components"; import { AvatarComponent } from "@ui/components/avatar/avatar.component"; @@ -10,11 +9,9 @@ import { ModalComponent } from "@ui/components/modal/modal.component"; import { SubscriptionService } from "@office/services/subscription.service"; import { InviteService } from "@office/services/invite.service"; import { ClickOutsideModule } from "ng-click-outside"; -import { Router } from "@angular/router"; -import { User } from "@auth/models/user.model"; +import { Router, RouterLink } from "@angular/router"; import { TagComponent } from "@ui/components/tag/tag.component"; import { YearsFromBirthdayPipe } from "@corelib"; -import { Invite } from "@office/models/invite.model"; /** * Компонент карточки информации с разным наполнением, в зависимости от контекста @@ -34,6 +31,7 @@ import { Invite } from "@office/models/invite.model"; ClickOutsideModule, TagComponent, YearsFromBirthdayPipe, + RouterLink, ], }) export class InfoCardComponent implements OnInit { @@ -59,9 +57,7 @@ export class InfoCardComponent implements OnInit { inviteErrorModal = false; haveBadge = this.calculateHaveBadge(); - ngOnInit(): void { - this.validateInputs(); - } + ngOnInit(): void {} /** * Определяет, нужно ли показывать информацию о проекте @@ -241,13 +237,4 @@ export class InfoCardComponent implements OnInit { location.href.includes("/projects") ); } - - /** - * Валидация входных параметров - */ - private validateInputs(): void { - if (this.appereance !== "empty" && !this.info) { - console.warn('ProjectCardComponent: project is required when appearance is not "empty"'); - } - } } diff --git a/projects/social_platform/src/app/office/features/news-card/news-card.component.html b/projects/social_platform/src/app/office/features/news-card/news-card.component.html index 35ad994ec..de444d11c 100644 --- a/projects/social_platform/src/app/office/features/news-card/news-card.component.html +++ b/projects/social_platform/src/app/office/features/news-card/news-card.component.html @@ -17,9 +17,9 @@ @if (menuOpen) {
      @if (!editMode) { -
    • Редактировать
    • +
    • редактировать
    • } -
    • Удалить
    • +
    • удалить
    }
    diff --git a/projects/social_platform/src/app/office/feed/feed.component.html b/projects/social_platform/src/app/office/feed/feed.component.html index 74d485713..10204d50e 100644 --- a/projects/social_platform/src/app/office/feed/feed.component.html +++ b/projects/social_platform/src/app/office/feed/feed.component.html @@ -3,7 +3,7 @@
    - @for (item of feedItems(); track $index) { @if (item.typeModel === "vacancy") { + @for (item of feedItems(); track item.content.id) { @if (item.typeModel === "vacancy") { r["data"])) .subscribe((feed: ApiPagination) => { this.feedItems.set(feed.results); this.totalItemsCount.set(feed.count); + this.feedPage.set(feed.results.length); - this.initObserver(); + setTimeout(() => { + const observer = new IntersectionObserver(this.onFeedItemView.bind(this), { + root: document.querySelector(".office__body"), + rootMargin: "0px 0px 0px 0px", + threshold: 0, + }); + + document.querySelectorAll(".page__item").forEach(e => { + observer.observe(e); + }); + }); }); this.subscriptions$().push(routeData$); const queryParams$ = this.route.queryParams .pipe( - map(p => p["includes"]), - tap(includes => this.includes.set(includes)), + map(params => params["includes"]), + tap(includes => { + this.includes.set(includes); + }), skip(1), concatMap(includes => { this.totalItemsCount.set(0); - this.feedPage.set(1); + this.feedPage.set(0); + return this.onFetch(0, this.perFetchTake(), includes ?? ["vacancy", "projects", "news"]); }) ) .subscribe(feed => { this.feedItems.set(feed); + this.feedPage.set(feed.length); + setTimeout(() => { - this.feedRoot?.nativeElement.scrollTo({ top: 0 }); - this.observeNewElements(); + this.feedRoot?.nativeElement.children[0].scrollIntoView({ behavior: "smooth" }); }); }); this.subscriptions$().push(queryParams$); } - /** - * НАСТРОЙКА БЕСКОНЕЧНОЙ ПРОКРУТКИ - * - * ЧТО ДЕЛАЕТ: - * - Подписывается на события прокрутки - * - Загружает новые элементы при достижении конца списка - */ ngAfterViewInit() { const target = document.querySelector(".office__body"); if (target) { @@ -131,39 +106,25 @@ export class FeedComponent implements OnInit, AfterViewInit, OnDestroy { } ngOnDestroy() { - this.subscriptions$().forEach(s => s.unsubscribe()); - this.observer?.disconnect(); + this.subscriptions$().forEach($ => $.unsubscribe()); } @ViewChild("feedRoot") feedRoot?: ElementRef; - // Сигналы состояния компонента - totalItemsCount = signal(0); // Общее количество элементов - feedItems = signal([]); // Массив элементов ленты - feedPage = signal(1); // Текущая страница - perFetchTake = signal(20); // Количество элементов за запрос - includes = signal([]); // Активные фильтры + totalItemsCount = signal(0); + feedItems = signal([]); + feedPage = signal(0); + perFetchTake = signal(20); + includes = signal([]); subscriptions$ = signal([]); - /** - * ОБРАБОТКА ЛАЙКОВ НОВОСТЕЙ - * - * ЧТО ПРИНИМАЕТ: - * @param newsId - ID новости для лайка/дизлайка - * - * ЧТО ДЕЛАЕТ: - * - Переключает состояние лайка - * - Обновляет счетчик лайков - * - Различает новости проектов и профилей - */ onLike(newsId: number) { const itemIdx = this.feedItems().findIndex(n => n.content.id === newsId); const item = this.feedItems()[itemIdx]; if (!item || item.typeModel !== "news") return; - // Определяем тип новости по структуре contentObject if ("email" in item.content.contentObject) { this.profileNewsService .toggleLike( @@ -205,17 +166,7 @@ export class FeedComponent implements OnInit, AfterViewInit, OnDestroy { } } - /** - * ОТСЛЕЖИВАНИЕ ПРОСМОТРОВ ЭЛЕМЕНТОВ - * - * ЧТО ПРИНИМАЕТ: - * @param entries - массив элементов, попавших в область видимости - * - * ЧТО ДЕЛАЕТ: - * - Отмечает новости как прочитанные при попадании в область видимости - * - Различает новости проектов и профилей - */ - private onFeedItemView(entries: IntersectionObserverEntry[]): void { + onFeedItemView(entries: IntersectionObserverEntry[]): void { const items = entries .map(e => { return Number((e.target as HTMLElement).dataset["id"]); @@ -230,7 +181,6 @@ export class FeedComponent implements OnInit, AfterViewInit, OnDestroy { item => item.typeModel === "news" && "email" in item.content.contentObject ); - // Отмечаем новости проектов как прочитанные projectNews.forEach(news => { if (news.typeModel !== "news") return; this.projectNewsService @@ -238,7 +188,6 @@ export class FeedComponent implements OnInit, AfterViewInit, OnDestroy { .subscribe(noop); }); - // Отмечаем новости профилей как прочитанные profileNews.forEach(news => { if (news.typeModel !== "news") return; this.profileNewsService @@ -247,65 +196,37 @@ export class FeedComponent implements OnInit, AfterViewInit, OnDestroy { }); } - /** - * ОБРАБОТКА ПРОКРУТКИ ДЛЯ БЕСКОНЕЧНОЙ ЗАГРУЗКИ - * - * ЧТО ВОЗВРАЩАЕТ: - * @returns Observable с новыми элементами или пустой объект - * - * ЧТО ДЕЛАЕТ: - * - Проверяет, достигнут ли конец списка - * - Загружает следующую порцию элементов при необходимости - */ - /** - * ОБРАБОТКА ПРОКРУТКИ ДЛЯ БЕСКОНЕЧНОЙ ЗАГРУЗКИ - */ - private onScroll() { - const container = document.querySelector(".office__body") as HTMLElement; - if (!container) return of({}); + onScroll() { + if (this.totalItemsCount() && this.feedItems().length >= this.totalItemsCount()) return of({}); - const isNearBottom = - container.scrollHeight - container.scrollTop - container.clientHeight < 100; + const target = document.querySelector(".office__body"); + if (!target || !this.feedRoot) return of({}); - if (!isNearBottom) return of({}); + const diff = + target.scrollTop - + this.feedRoot.nativeElement.getBoundingClientRect().height + + window.innerHeight; - // Предотвращаем множественные запросы - if (this.feedItems().length >= this.totalItemsCount()) { - return of([]); - } + if (diff > 0) { + const currentOffset = this.feedItems().length; - return this.onFetch( - this.feedPage() * this.perFetchTake(), - this.perFetchTake(), - this.includes() - ).pipe( - tap((feedChunk: FeedItem[]) => { - if (feedChunk.length > 0) { - this.feedPage.update(p => p + 1); - this.feedItems.update(items => [...items, ...feedChunk]); + return this.onFetch(currentOffset, this.perFetchTake(), this.includes()).pipe( + tap((feedChunk: FeedItem[]) => { + const existingIds = new Set(this.feedItems().map(item => item.content.id)); + const uniqueNewItems = feedChunk.filter(item => !existingIds.has(item.content.id)); - // ВАЖНО: обновляем observer после добавления новых элементов - setTimeout(() => { - this.observeNewElements(); - this.cdref.detectChanges(); - }, 0); - } - }) - ); + if (uniqueNewItems.length > 0) { + this.feedPage.update(page => page + uniqueNewItems.length); + this.feedItems.update(items => [...items, ...uniqueNewItems]); + } + }) + ); + } + + return of({}); } - /** - * ЗАГРУЗКА ЭЛЕМЕНТОВ ЛЕНТЫ - * - * ЧТО ПРИНИМАЕТ: - * @param offset - смещение для пагинации - * @param limit - количество элементов для загрузки - * @param includes - типы элементов для включения в результат - * - * ЧТО ВОЗВРАЩАЕТ: - * @returns Observable - массив элементов ленты - */ - private onFetch( + onFetch( offset: number, limit: number, includes: FeedItemType[] = ["project", "vacancy", "news"] @@ -317,33 +238,4 @@ export class FeedComponent implements OnInit, AfterViewInit, OnDestroy { map(res => res.results) ); } - - private initObserver() { - if (this.observer) { - this.observer.disconnect(); - } - - this.observer = new IntersectionObserver(this.onFeedItemView.bind(this), { - root: null, - rootMargin: "0px", - threshold: 0.1, - }); - - this.observeNewElements(); - } - - /** - * ДОБАВЛЕНИЕ НОВЫХ ЭЛЕМЕНТОВ - */ - private observeNewElements() { - // Небольшая задержка для рендеринга DOM - setTimeout(() => { - document.querySelectorAll(".page__item").forEach(element => { - if (element && !element.hasAttribute("data-observed")) { - this.observer?.observe(element); - element.setAttribute("data-observed", "true"); - } - }); - }, 0); - } } diff --git a/projects/social_platform/src/app/office/profile/detail/main/main.component.html b/projects/social_platform/src/app/office/profile/detail/main/main.component.html index 7bc19a659..36c9f8fc0 100644 --- a/projects/social_platform/src/app/office/profile/detail/main/main.component.html +++ b/projects/social_platform/src/app/office/profile/detail/main/main.component.html @@ -70,7 +70,7 @@

    языки

    diff --git a/projects/social_platform/src/app/office/profile/detail/main/main.component.scss b/projects/social_platform/src/app/office/profile/detail/main/main.component.scss index c93650b63..6f5c6b430 100644 --- a/projects/social_platform/src/app/office/profile/detail/main/main.component.scss +++ b/projects/social_platform/src/app/office/profile/detail/main/main.component.scss @@ -343,6 +343,10 @@ &--more { color: var(--accent) !important; } + + img { + border-radius: var(--rounded-xxl); + } } &__date { @@ -430,7 +434,6 @@ width: 350px; height: 175px; max-height: calc(100vh - 40px); - padding: 24px; overflow-y: auto; &__top { diff --git a/projects/social_platform/src/app/office/program/detail/list/list.component.html b/projects/social_platform/src/app/office/program/detail/list/list.component.html index 5267e4f67..ae3628d83 100644 --- a/projects/social_platform/src/app/office/program/detail/list/list.component.html +++ b/projects/social_platform/src/app/office/program/detail/list/list.component.html @@ -1,6 +1,10 @@ -
    +
    + @if (listType !== 'members') {
    @if (listType === 'projects') { @@ -61,4 +66,5 @@ }
    + }
    diff --git a/projects/social_platform/src/app/office/program/detail/list/list.component.scss b/projects/social_platform/src/app/office/program/detail/list/list.component.scss index 6332ff14e..b90b4a34e 100644 --- a/projects/social_platform/src/app/office/program/detail/list/list.component.scss +++ b/projects/social_platform/src/app/office/program/detail/list/list.component.scss @@ -138,7 +138,7 @@ bottom: 0; left: 0; z-index: 10; - max-height: 72vh; + min-height: 72vh; overflow-y: auto; background-color: var(--white); border-radius: var(--rounded-lg); diff --git a/projects/social_platform/src/app/office/program/detail/list/list.component.ts b/projects/social_platform/src/app/office/program/detail/list/list.component.ts index ffe667cf6..f814bba41 100644 --- a/projects/social_platform/src/app/office/program/detail/list/list.component.ts +++ b/projects/social_platform/src/app/office/program/detail/list/list.component.ts @@ -142,6 +142,8 @@ export class ProgramListComponent implements OnInit, OnDestroy, AfterViewInit { this.listType = data["listType"]; }); + console.log(this.listType); + const routeData$ = this.route.data.pipe(map(r => r["data"])).subscribe(data => { this.listTotalCount = data.count; this.list = data.results; diff --git a/projects/social_platform/src/app/office/program/detail/main/main.component.html b/projects/social_platform/src/app/office/program/detail/main/main.component.html index b4c9e3673..a9be3ea2a 100644 --- a/projects/social_platform/src/app/office/program/detail/main/main.component.html +++ b/projects/social_platform/src/app/office/program/detail/main/main.component.html @@ -42,7 +42,7 @@

    о программе

    }
    - @if (program.isUserManager) { + @if (program.isUserManager && !program.isUserMember) { } @for (n of news(); track n.id) { о проекте [icon]="directionItem.icon" [about]="directionItem.about" [type]="directionItem.type" + [isOwner]="profile.id === project.leader" [projectInfoType]="directionItem.direction === 'цели' ? 'goals' : 'partners'" > } diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.html b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.html index b263a3c38..bddd77e07 100644 --- a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.html +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.html @@ -75,14 +75,40 @@ } } @else { @if (projectInfoType === 'goals') {
    - @if (this.about.length > 0) { @for (goalItem of about; track $index) { + @if (!isShowsConfirmGoal) { @if (about.length > 0) { @for (goalItem of about; track $index) {
  • {{ goalItem.title }}

    -

    - {{ goalItem.completionDate | dayjs: "format":"DD.MM.YY" }} -

    +
    + @if (!goalItem.isDone) { +
    + @if (goalCompleteHoverId === goalItem.id) { + + } @else { +

    + {{ goalItem.completionDate | dayjs: "format":"DD.MM.YY" }} +

    + } +
    + } @else { + + } +
  • + } } } @else { @if (selectedGoal) { +
    +

    подтвердить выполнение цели

    + +
    + + + подтверждаю + } }
    } @else { diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss index 1f4ca1a50..9a740601f 100644 --- a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss @@ -40,9 +40,8 @@ display: flex; flex-direction: column; width: 350px; - height: 175px; + height: 200px; max-height: calc(100vh - 40px); - padding: 24px; overflow-y: auto; &__top { @@ -54,6 +53,23 @@ border-bottom: 0.5px solid var(--accent); } + &__confirm { + display: flex; + align-items: center; + flex-direction: column; + justify-content: center; + gap: 36px; + margin-bottom: 16px; + + p { + color: var(--black); + } + + i { + color: var(--green); + } + } + &__title { color: var(--accent); text-align: center; @@ -141,6 +157,10 @@ display: flex; align-items: center; justify-content: space-between; + + &--complete { + color: var(--green); + } } } @@ -158,9 +178,25 @@ align-items: center; &--status { - padding: 8px; + padding: 5px; + text-align: center; + width: 45px; border: 0.5px solid var(--medium-grey-for-outline); border-radius: var(--rounded-xl); + color: var(--accent); + cursor: pointer; + + &--complete { + border: 0.5px solid var(--green); + i { + color: var(--green); + } + } + + i { + display: flex; + justify-content: center; + } } &--title { diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.ts b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.ts index a4f5d5ea6..6db64671c 100644 --- a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.ts +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.ts @@ -13,6 +13,10 @@ import { FileItemComponent } from "@ui/components/file-item/file-item.component" import { FileModel } from "@office/models/file.model"; import { AvatarComponent } from "@ui/components/avatar/avatar.component"; import { DayjsPipe } from "@corelib"; +import { ButtonComponent } from "@ui/components"; +import { ProjectService } from "@office/services/project.service"; +import { Goal } from "@office/models/goals.model"; +import { EditorSubmitButtonDirective } from "@ui/directives/editor-submit-button.directive"; @Component({ selector: "app-project-direction-card", templateUrl: "./project-direction-card.component.html", @@ -25,6 +29,8 @@ import { DayjsPipe } from "@corelib"; FileItemComponent, AvatarComponent, DayjsPipe, + ButtonComponent, + EditorSubmitButtonDirective, ], standalone: true, }) @@ -33,6 +39,7 @@ export class ProjectDirectionCard implements OnInit, OnDestroy { @Input() icon!: string; @Input() about!: string | any[]; @Input() type!: string; + @Input() isOwner!: boolean; @Input() profileInfoType?: "skills" | "achievements"; @Input() projectInfoType?: "goals" | "partners"; @@ -40,11 +47,18 @@ export class ProjectDirectionCard implements OnInit, OnDestroy { private readonly route = inject(ActivatedRoute); private readonly router = inject(Router); private readonly achievementsService = inject(ProfileService); + private readonly projectService = inject(ProjectService); private subscriptions: Subscription[] = []; isShowModal = false; + isShowsConfirmGoal = false; + isOpenInfo = false; + + goalCompleteHoverId: null | number = null; + selectedGoal: Goal | null = null; + listType?: "profile" | "project"; // Поля для работы с достижениями @@ -53,6 +67,20 @@ export class ProjectDirectionCard implements OnInit, OnDestroy { achievementsInfo = signal([]); currentYear = 0; + mouseHover(goalId: number): void { + if (this.isOwner) { + if (goalId) { + this.goalCompleteHoverId = goalId; + } + } + } + + mouseLeave(): void { + if (this.isOwner) { + this.goalCompleteHoverId = null; + } + } + ngOnInit(): void { const listTypeSub$ = this.route.data.subscribe(data => { this.listType = data["listType"]; @@ -74,6 +102,55 @@ export class ProjectDirectionCard implements OnInit, OnDestroy { this.subscriptions.forEach($ => $.unsubscribe()); } + openConfirmModal(goal: Goal): void { + this.selectedGoal = goal; + this.isShowsConfirmGoal = true; + this.router.navigate([], { + queryParams: { goalId: goal.id }, + relativeTo: this.route, + queryParamsHandling: "merge", + }); + } + + confirmCompleteGoal(): void { + const projectId = this.route.snapshot.params["projectId"]; + const goalId = +this.route.snapshot.queryParams["goalId"]; + + if (!goalId || !Array.isArray(this.about)) return; + + const goal = this.about.find((g: Goal) => g.id === goalId); + + if (!goal) return; + + const completedGoal = { + ...goal, + isDone: true, + }; + + this.projectService.editGoal(projectId, goal.id, completedGoal).subscribe({ + next: response => { + if (Array.isArray(this.about)) { + const index = this.about.findIndex((g: Goal) => g.id === goalId); + if (index !== -1) { + this.about[index] = response; + } + } + + this.isShowsConfirmGoal = false; + this.goalCompleteHoverId = null; + this.selectedGoal = null; + + this.router.navigate([], { + queryParams: {}, + replaceUrl: true, + }); + }, + error: error => { + console.error("Ошибка при обновлении цели:", error); + }, + }); + } + openInfo(achievementYear: string): void { this.router.navigate([], { queryParams: { 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 44712ded7..a066833fe 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 @@ -289,10 +289,13 @@ } @if (goals.at(i)?.get("completionDate"); as goalDate) {
    - + } @if (goals.at(i)?.get("responsible"); as goalLeader) {
    - + @if (getSelectedLeaderForGoal(i); as selectedLeader) {
    diff --git a/projects/social_platform/src/app/office/projects/projects.component.html b/projects/social_platform/src/app/office/projects/projects.component.html index a5f080288..e3944af32 100644 --- a/projects/social_platform/src/app/office/projects/projects.component.html +++ b/projects/social_platform/src/app/office/projects/projects.component.html @@ -2,7 +2,10 @@
    - +
    diff --git a/projects/social_platform/src/app/office/services/project.service.ts b/projects/social_platform/src/app/office/services/project.service.ts index c4be7039e..b99c0444a 100644 --- a/projects/social_platform/src/app/office/services/project.service.ts +++ b/projects/social_platform/src/app/office/services/project.service.ts @@ -195,7 +195,7 @@ export class ProjectService { * Редактирование цели */ editGoal(projectId: number, goalId: number, params: GoalPostForm) { - return this.apiService.post(`${this.PROJECTS_URL}/${projectId}/goals/${goalId}`, params); + return this.apiService.put(`${this.PROJECTS_URL}/${projectId}/goals/${goalId}`, params); } /** diff --git a/projects/social_platform/src/app/office/vacancies/list/list.component.scss b/projects/social_platform/src/app/office/vacancies/list/list.component.scss index 0d466d507..7e0a4913a 100644 --- a/projects/social_platform/src/app/office/vacancies/list/list.component.scss +++ b/projects/social_platform/src/app/office/vacancies/list/list.component.scss @@ -22,16 +22,17 @@ .cancel { display: flex; flex-direction: column; + justify-content: center; width: 350px; height: 175px; - max-height: calc(100vh - 40px); - padding: 24px; + max-height: calc(100vh - 40px);; overflow-y: auto; &__top { display: flex; flex-direction: column; align-items: center; + gap: 8px; justify-content: space-between; padding-bottom: 8px; margin-bottom: 8px; diff --git a/projects/social_platform/src/app/ui/components/input/input.component.scss b/projects/social_platform/src/app/ui/components/input/input.component.scss index e825d3c2d..97433c750 100644 --- a/projects/social_platform/src/app/ui/components/input/input.component.scss +++ b/projects/social_platform/src/app/ui/components/input/input.component.scss @@ -19,7 +19,7 @@ &--small { max-width: 70px; - padding: 12px; + padding: 12px 0px; text-align: center; &--error { diff --git a/projects/ui/src/lib/components/primitives/back/back.component.scss b/projects/ui/src/lib/components/primitives/back/back.component.scss index 9613e0f95..1b2f14b77 100644 --- a/projects/ui/src/lib/components/primitives/back/back.component.scss +++ b/projects/ui/src/lib/components/primitives/back/back.component.scss @@ -4,11 +4,11 @@ display: flex; align-items: center; margin-bottom: 45px; + cursor: pointer; &__icon { flex-shrink: 0; color: var(--grey-for-text); - cursor: pointer; } span { From 4dc5b35b45ac91fa3604337998c33a3dc0373e69 Mon Sep 17 00:00:00 2001 From: Awakich Date: Thu, 30 Oct 2025 13:22:04 +0300 Subject: [PATCH 119/126] add styles for confirm goals modal, small-input --- .../app/office/features/detail/detail.component.scss | 2 +- .../project-direction-card.component.scss | 11 ++++++----- .../src/app/office/vacancies/list/list.component.scss | 4 ++-- .../src/app/ui/components/input/input.component.scss | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) 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 68e3d5d5d..00c844ea1 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 @@ -11,8 +11,8 @@ $detail-bar-mb: 12px; flex-direction: column; height: 100%; max-height: 100%; - overflow-y: scroll; padding-top: 20px; + overflow-y: scroll; &__body { flex-grow: 1; diff --git a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss index 9a740601f..3e7a01580 100644 --- a/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss +++ b/projects/social_platform/src/app/office/projects/detail/shared/project-direction-card/project-direction-card.component.scss @@ -55,10 +55,10 @@ &__confirm { display: flex; - align-items: center; flex-direction: column; - justify-content: center; gap: 36px; + align-items: center; + justify-content: center; margin-bottom: 16px; p { @@ -178,16 +178,17 @@ align-items: center; &--status { + width: 45px; padding: 5px; + color: var(--accent); text-align: center; - width: 45px; + cursor: pointer; border: 0.5px solid var(--medium-grey-for-outline); border-radius: var(--rounded-xl); - color: var(--accent); - cursor: pointer; &--complete { border: 0.5px solid var(--green); + i { color: var(--green); } diff --git a/projects/social_platform/src/app/office/vacancies/list/list.component.scss b/projects/social_platform/src/app/office/vacancies/list/list.component.scss index 7e0a4913a..dfb6f78b5 100644 --- a/projects/social_platform/src/app/office/vacancies/list/list.component.scss +++ b/projects/social_platform/src/app/office/vacancies/list/list.component.scss @@ -25,14 +25,14 @@ justify-content: center; width: 350px; height: 175px; - max-height: calc(100vh - 40px);; + max-height: calc(100vh - 40px); overflow-y: auto; &__top { display: flex; flex-direction: column; - align-items: center; gap: 8px; + align-items: center; justify-content: space-between; padding-bottom: 8px; margin-bottom: 8px; diff --git a/projects/social_platform/src/app/ui/components/input/input.component.scss b/projects/social_platform/src/app/ui/components/input/input.component.scss index 97433c750..c970ca070 100644 --- a/projects/social_platform/src/app/ui/components/input/input.component.scss +++ b/projects/social_platform/src/app/ui/components/input/input.component.scss @@ -19,7 +19,7 @@ &--small { max-width: 70px; - padding: 12px 0px; + padding: 12px 0; text-align: center; &--error { From c7af7a528df99f105af5ddafe8dbc17c0ee5a7d4 Mon Sep 17 00:00:00 2001 From: Awakich Date: Thu, 30 Oct 2025 23:28:39 +0300 Subject: [PATCH 120/126] fix adding resource & removing partner --- .../project-partner-resources-step.component.html | 2 +- .../project-partner-resources-step.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.html b/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.html index 535494817..d1ce6271f 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.html +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.html @@ -80,7 +80,7 @@ type="icon" class="project__info--remove" color="red" - (click)="removePartner(i, partners.value[i].id)" + (click)="removePartner(i, partners.value[i].company.id)" > diff --git a/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.ts b/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.ts index 229d534e6..158defc4f 100644 --- a/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.ts +++ b/projects/social_platform/src/app/office/projects/edit/shared/project-partner-resources-step/project-partner-resources-step.component.ts @@ -105,7 +105,7 @@ export class ProjectPartnerResourcesStepComponent implements OnDestroy { const partnerOptions: optionsListElement[] = partners.map((partner: any, index: number) => ({ id: partner.company.id ?? index, - value: partner.id ?? null, + value: partner.company.id ?? null, label: partner.name, })); From 95bdcb6231ff39cb59d2a6f253f23e6d74bd332b Mon Sep 17 00:00:00 2001 From: Awakich Date: Fri, 31 Oct 2025 16:14:50 +0300 Subject: [PATCH 121/126] add partner-project icon to info-card component, fix buttons in onboarding & error class & specs & skills picking groups & detail permisions --- .../src/app/error/models/error-message.ts | 2 +- .../features/detail/detail.component.html | 87 +++++++++++++++---- .../features/detail/detail.component.ts | 25 +++++- .../info-card/info-card.component.html | 33 +++++-- .../info-card/info-card.component.scss | 38 +++++++- .../features/info-card/info-card.component.ts | 2 + .../stage-one/stage-one.component.html | 44 +++++----- .../stage-one/stage-one.component.scss | 3 +- .../stage-one/stage-one.component.ts | 27 ++++++ .../stage-two/stage-two.component.html | 44 +++++----- .../stage-two/stage-two.component.scss | 3 +- .../stage-two/stage-two.component.ts | 27 ++++++ .../stage-zero/stage-zero.component.html | 11 ++- .../stage-zero/stage-zero.component.scss | 21 +++++ .../stage-zero/stage-zero.component.ts | 19 ++-- .../office/program/models/program.model.ts | 2 + .../project-main-step.component.html | 2 +- .../specializations-group.component.html | 38 +++++--- .../specializations-group.component.scss | 42 ++++++++- .../specializations-group.component.ts | 40 +++++++-- 20 files changed, 398 insertions(+), 112 deletions(-) diff --git a/projects/social_platform/src/app/error/models/error-message.ts b/projects/social_platform/src/app/error/models/error-message.ts index a5b3606b6..b4c679926 100644 --- a/projects/social_platform/src/app/error/models/error-message.ts +++ b/projects/social_platform/src/app/error/models/error-message.ts @@ -35,7 +35,7 @@ export enum ErrorMessage { VALIDATION_LANGUAGE = "Используйте символы кириллического алфавита", VALIDATION_EMAIL = "Введенное значение не соответствует формату email", VALIDATION_PASSWORD_UNMATCH = "Пароли не совпадают", - EMPTY_AVATAR = "*Выберите фото для профиля", + EMPTY_AVATAR = "Выберите фото для профиля", VALIDATION_PATTERN = "Введите корректную ссылку, начинающуюся с http:// или https://", // Ошибки приглашений в проект 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 2297d607f..85768755c 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 @@ -61,20 +61,43 @@
    - @if (!isUserMember && !isUserManager && !registerDateExpired) { @if - (info().name.includes("Кейс-чемпионат MIR")) { + @if (!isUserMember && !isUserManager) { @if (info().name.includes("Кейс-чемпионат MIR")) { - + + зарегистрироваться + + + } @else if (info().registrationLink) { + + зарегистрироваться } @else { - + зарегистрироваться - } } @else if (isUserMember && isUserManager) { + } } @else if (isUserMember && !isUserManager && isUserExpert) { подать проект - } @else if (isUserManager && !isUserMember) { + } @else if (isUserManager) { @if (isUserMember) { - } + } } @@ -151,6 +175,8 @@ проекты-участники @@ -172,6 +198,8 @@ участники @@ -182,10 +210,17 @@
    -

    выберите проект для подачи

    +

    + {{ + memberProjects.length > 0 ? "выберите проект для подачи" : "создай свой проект!" + }} +

    - после выбора проекта будет создан дубликат данного проекта для заполнения под - конкретный конкурс + {{ + memberProjects.length > 0 + ? "после выбора проекта будет создан дубликат данного проекта для заполнения под конкретный конкурс" + : "создай проект и не забудь вернуться в программу для его подачи" + }}

    @@ -213,15 +248,29 @@
    - - выбрать проект - +
    + @if (memberProjects.length > 0) { + + выбрать проект + + +

    или

    + } + + создать новый проект +
    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 271620627..a37b0f1c4 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 @@ -24,8 +24,8 @@ import { calculateProfileProgress } from "@utils/calculateProgress"; import { ProfileDataService } from "@office/profile/detail/services/profile-date.service"; import { ProfileService } from "projects/skills/src/app/profile/services/profile.service"; import { SnackbarService } from "@ui/services/snackbar.service"; -import { PluralizePipe } from "@corelib"; import { ApproveSkillComponent } from "../approve-skill/approve-skill.component"; +import { ProjectsService } from "@office/projects/services/projects.service"; @Component({ selector: "app-detail", @@ -57,6 +57,7 @@ export class DeatilComponent implements OnInit, OnDestroy { private readonly location = inject(Location); private readonly profileDataService = inject(ProfileDataService); public readonly skillsProfileService = inject(ProfileService); + private readonly projectsService = inject(ProjectsService); public readonly chatService = inject(ChatService); private readonly cdRef = inject(ChangeDetectorRef); @@ -93,6 +94,8 @@ export class DeatilComponent implements OnInit, OnDestroy { dubplicatedProjectId = 0; memberProjects: Project[] = []; + userType = 0; + // Сигналы для работы с модальными окнами с текстом assignProjectToProgramModalMessage = signal(null); errorMessageModal = signal(""); @@ -147,6 +150,10 @@ export class DeatilComponent implements OnInit, OnDestroy { } } + get isUserExpert() { + return this.userType !== 1; + } + // Методы для управления состоянием ошибок через сервис setAssignProjectToProgramError(error: { non_field_errors: string[] }): void { this.projectAdditionalService.setAssignProjectToProgramError(error); @@ -200,6 +207,10 @@ export class DeatilComponent implements OnInit, OnDestroy { this.assignProjectToProgram(selectedProject!); } + addNewProject(): void { + this.projectsService.addProject(); + } + /** Эмитим логику для привязки проекта к программе */ /** * Привязка проекта к программе выбранной @@ -385,6 +396,17 @@ export class DeatilComponent implements OnInit, OnDestroy { ) .subscribe(); + const profileDataSub$ = this.profileDataService + .getProfile() + .pipe(filter(user => !!user)) + .subscribe({ + next: user => { + if (user) { + this.userType = user.userType; + } + }, + }); + const memeberProjects$ = this.projectService.getMy().subscribe({ next: projects => { this.memberProjects = projects.results.filter(project => !project.draft); @@ -393,6 +415,7 @@ export class DeatilComponent implements OnInit, OnDestroy { this.subscriptions.push(program$); this.subscriptions.push(memeberProjects$); + this.subscriptions.push(profileDataSub$); } else { const profileDataSub$ = this.profileDataService .getProfile() diff --git a/projects/social_platform/src/app/office/features/info-card/info-card.component.html b/projects/social_platform/src/app/office/features/info-card/info-card.component.html index 8a48306a1..27884d4b2 100644 --- a/projects/social_platform/src/app/office/features/info-card/info-card.component.html +++ b/projects/social_platform/src/app/office/features/info-card/info-card.component.html @@ -128,14 +128,31 @@
    @if (type === 'projects') { - - проект - +
    + + проект + + + @if (info.partnerProgramId) { +
    + +
    + + @if (programProjectHovered) { +
    +

    проект привязан к программе

    +
    + } } +
    } @else if (type === 'members') { Кем хочешь работать? (hide)="hideTooltip('auth')" >
    + +
    + закончить регистрацию позже + продолжить +
    -
    - {{ title }} - -
    -@if (contentVisible) { -
    - @for (opt of options; track opt.id) { -
    - {{ opt.name }} + +
    +
    + {{ title }} + @if (!disabled) { + + } +
    + @if (contentVisible()) { +
    + @for (opt of options; track opt.id) { +
    + +
    + }
    }
    -} diff --git a/projects/social_platform/src/app/office/shared/specializations-group/specializations-group.component.scss b/projects/social_platform/src/app/office/shared/specializations-group/specializations-group.component.scss index 176303b36..a76ded2e2 100644 --- a/projects/social_platform/src/app/office/shared/specializations-group/specializations-group.component.scss +++ b/projects/social_platform/src/app/office/shared/specializations-group/specializations-group.component.scss @@ -9,16 +9,43 @@ display: flex; align-items: center; justify-content: space-between; + width: 100%; height: 32px; cursor: pointer; + transition: opacity 0.2s ease; i { - transform: rotate(90deg); + transition: transform 0.2s ease; + transform: rotate(180deg); } &--open { i { - transform: rotate(180deg); + transform: rotate(90deg); + } + } + + &--disabled { + pointer-events: none; + cursor: not-allowed; + opacity: 0.5; + } + + &__top { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + transition: width 0.2s ease; + + .heading--open &, + &--has-open { + width: 50%; + } + + &--disabled { + cursor: not-allowed; + opacity: 0.5; } } } @@ -28,14 +55,25 @@ flex-direction: column; gap: 5px; + &--open { + width: 40%; + } + &__option { padding: 5px 0; padding-left: 10px; cursor: pointer; + transition: opacity 0.2s ease; &:hover { background-color: var(--light-gray); } + + &--disabled { + pointer-events: none; + cursor: not-allowed; + opacity: 0.5; + } } } } diff --git a/projects/social_platform/src/app/office/shared/specializations-group/specializations-group.component.ts b/projects/social_platform/src/app/office/shared/specializations-group/specializations-group.component.ts index f16453198..70bb7cb3f 100644 --- a/projects/social_platform/src/app/office/shared/specializations-group/specializations-group.component.ts +++ b/projects/social_platform/src/app/office/shared/specializations-group/specializations-group.component.ts @@ -1,7 +1,14 @@ /** @format */ import { CommonModule } from "@angular/common"; -import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from "@angular/core"; +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + Output, + signal, +} from "@angular/core"; import { IconComponent } from "@ui/components"; import { Specialization } from "@office/models/specialization"; @@ -12,17 +19,19 @@ import { Specialization } from "@office/models/specialization"; * - Отображает заголовок группы специализаций * - Показывает/скрывает список специализаций при клике на заголовок * - Позволяет выбирать специализацию из списка + * - Использует Angular Signals для реактивности * - Использует OnPush стратегию для оптимизации производительности + * - Поддерживает disabled состояние когда открыты другие группы * * Входные параметры: * @Input title - заголовок группы специализаций (обязательный) * @Input options - массив специализаций для отображения (обязательный) + * @Input disabled - флаг отключения взаимодействия с группой + * @Input hasOpenGroups - флаг наличия открытых групп для адаптации ширины * * Выходные события: * @Output selectOption - событие выбора специализации, передает выбранную специализацию - * - * Внутренние свойства: - * - contentVisible - флаг видимости содержимого группы + * @Output groupToggled - событие переключения видимости группы */ @Component({ selector: "app-specializations-group", @@ -35,15 +44,36 @@ import { Specialization } from "@office/models/specialization"; export class SpecializationsGroupComponent { @Input({ required: true }) title!: string; @Input({ required: true }) options!: Specialization[]; + @Input() hasOpenGroups = false; + @Input() disabled = false; @Output() selectOption = new EventEmitter(); + @Output() groupToggled = new EventEmitter(); - contentVisible = false; + contentVisible = signal(false); + + /** + * Переключение видимости содержимого группы + * Теперь учитывает disabled состояние + */ + toggleContentVisible() { + if (this.disabled) { + return; + } + + this.contentVisible.update(val => !val); + this.groupToggled.emit(this.contentVisible()); + } /** * Обработчик выбора специализации * Эмитит событие с выбранной специализацией + * Теперь учитывает disabled состояние */ onSelectOption(opt: Specialization) { + if (this.disabled) { + return; + } + this.selectOption.emit(opt); } } From 93593c1fdee5003f3814cea57bf0ef72bacf9edf Mon Sep 17 00:00:00 2001 From: Awakich Date: Fri, 31 Oct 2025 16:15:13 +0300 Subject: [PATCH 122/126] fix styles in onboarding --- .../features/info-card/info-card.component.scss | 14 +++++++------- .../onboarding/stage-two/stage-two.component.scss | 2 +- .../stage-zero/stage-zero.component.scss | 7 +++---- 3 files changed, 11 insertions(+), 12 deletions(-) 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 929fbfbc6..9bb411c62 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 @@ -37,19 +37,19 @@ &--program { display: flex; - align-items: center; gap: 5px; + align-items: center; justify-content: center; &-icon { - width: 16px; - height: 12px; - margin-top: 6px; display: flex; align-items: center; justify-content: center; - border-radius: var(--rounded-lg); + width: 16px; + height: 12px; + margin-top: 6px; border: 0.5px solid var(--medium-grey-for-outline); + border-radius: var(--rounded-lg); i { color: var(--accent); @@ -59,8 +59,8 @@ &--project-partner { position: absolute; - left: 30%; top: 67%; + left: 30%; padding: 3px 5px; border: 0.5px solid var(--medium-grey-for-outline); border-radius: var(--rounded-lg); @@ -93,6 +93,7 @@ } &__content { + position: relative; display: flex; flex: 1; flex-direction: column; @@ -101,7 +102,6 @@ padding-top: 40px; overflow: hidden; text-align: center; - position: relative; &--empty { padding-top: 60px; diff --git a/projects/social_platform/src/app/office/onboarding/stage-two/stage-two.component.scss b/projects/social_platform/src/app/office/onboarding/stage-two/stage-two.component.scss index 2659d4a51..921637a0f 100644 --- a/projects/social_platform/src/app/office/onboarding/stage-two/stage-two.component.scss +++ b/projects/social_platform/src/app/office/onboarding/stage-two/stage-two.component.scss @@ -105,8 +105,8 @@ grid-column: 1/2; gap: 20px; order: 3; - margin-top: auto; width: 60%; + margin-top: auto; @include responsive.apply-desktop { flex-direction: row; diff --git a/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.scss b/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.scss index bbed5aa11..7aa77c571 100644 --- a/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.scss +++ b/projects/social_platform/src/app/office/onboarding/stage-zero/stage-zero.component.scss @@ -63,11 +63,11 @@ &__buttons { position: sticky; + right: 100%; + bottom: 3%; display: flex; flex-direction: column; gap: 10px; - bottom: 3%; - right: 100%; width: 100%; margin-left: 280%; } @@ -193,11 +193,10 @@ i { } } - .error { color: var(--red) !important; i { - color: var(--red) !important; + color: var(--red) !important; } } From 217a7eb43b4d2aa8fff8fb7f8eb5d076be3bfcbe Mon Sep 17 00:00:00 2001 From: Awakich Date: Fri, 31 Oct 2025 17:32:19 +0300 Subject: [PATCH 123/126] fix permissions in news-form & buttons in submit project modal --- .../src/app/office/features/detail/detail.component.html | 5 ++++- .../src/app/office/program/detail/main/main.component.html | 2 +- 2 files changed, 5 insertions(+), 2 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 85768755c..efc46d749 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 @@ -187,6 +187,7 @@ @@ -248,13 +249,14 @@
    -
    +
    @if (memberProjects.length > 0) { выбрать проект @@ -264,6 +266,7 @@ } о программе }
    - @if (program.isUserManager && !program.isUserMember) { + @if (program.isUserManager) { } @for (n of news(); track n.id) { Date: Fri, 31 Oct 2025 23:43:24 +0300 Subject: [PATCH 124/126] fix specs-group and onboarding & button for expert --- .../app/auth/register/register.component.scss | 8 ++++++ .../features/detail/detail.component.html | 4 +-- .../stage-zero/stage-zero.component.scss | 7 +++-- .../office/profile/edit/edit.component.html | 5 +++- .../app/office/profile/edit/edit.component.ts | 27 +++++++++++++++++++ .../shared/news-card/news-card.component.scss | 1 + .../office/projects/list/list.component.ts | 16 ----------- 7 files changed, 45 insertions(+), 23 deletions(-) diff --git a/projects/social_platform/src/app/auth/register/register.component.scss b/projects/social_platform/src/app/auth/register/register.component.scss index dd0e54267..7606a9e60 100644 --- a/projects/social_platform/src/app/auth/register/register.component.scss +++ b/projects/social_platform/src/app/auth/register/register.component.scss @@ -90,3 +90,11 @@ color: var(--dark-grey); cursor: pointer; } + +.error { + color: var(--red) !important; + + i { + color: var(--red) !important; + } +} 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 efc46d749..ee5310807 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 @@ -97,7 +97,7 @@ зарегистрироваться - } } @else if (isUserMember && !isUserManager && isUserExpert) { + } } @else if (isUserMember && !isUserManager && !isUserExpert) { подать проект - } @else if (isUserManager) { @if (isUserMember) { + } @else if (isUserManager || isUserExpert) { @if (isUserMember) { более специальностей - @if (!isUserManager) { + @if (isUserManager || isUserExpert) { + + + проекты-участники + + + } @else { - } @else { - + } @if (isUserManager || isUserExpert) { + - проекты-участники + участники - } @if (!isUserManager) { + } @else { - } @else { - - - участники - - - } + } }
    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 a37b0f1c4..13ebc4198 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 @@ -94,7 +94,7 @@ export class DeatilComponent implements OnInit, OnDestroy { dubplicatedProjectId = 0; memberProjects: Project[] = []; - userType = 0; + userType = signal(undefined); // Сигналы для работы с модальными окнами с текстом assignProjectToProgramModalMessage = signal(null); @@ -151,7 +151,8 @@ export class DeatilComponent implements OnInit, OnDestroy { } get isUserExpert() { - return this.userType !== 1; + const type = this.userType(); + return type !== undefined && type !== 1; } // Методы для управления состоянием ошибок через сервис @@ -396,16 +397,12 @@ export class DeatilComponent implements OnInit, OnDestroy { ) .subscribe(); - const profileDataSub$ = this.profileDataService - .getProfile() - .pipe(filter(user => !!user)) - .subscribe({ - next: user => { - if (user) { - this.userType = user.userType; - } - }, - }); + const profileDataSub$ = this.authService.profile.pipe(filter(user => !!user)).subscribe({ + next: user => { + this.userType.set(user!.userType); + this.cdRef.detectChanges(); + }, + }); const memeberProjects$ = this.projectService.getMy().subscribe({ next: projects => { diff --git a/projects/social_platform/src/app/office/program/detail/shared/news-card/news-card.component.scss b/projects/social_platform/src/app/office/program/detail/shared/news-card/news-card.component.scss index 9a9c35b1d..3c819c820 100644 --- a/projects/social_platform/src/app/office/program/detail/shared/news-card/news-card.component.scss +++ b/projects/social_platform/src/app/office/program/detail/shared/news-card/news-card.component.scss @@ -166,8 +166,8 @@ &__left { display: flex; - align-items: center; gap: 5px; + align-items: center; } &__item { From d69fb1fc9e44c7d675a60d7e76d76eeec43b8173 Mon Sep 17 00:00:00 2001 From: Awakich Date: Sat, 1 Nov 2025 01:17:10 +0300 Subject: [PATCH 126/126] fix project-members --- .../src/app/office/program/detail/list/list.component.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/projects/social_platform/src/app/office/program/detail/list/list.component.scss b/projects/social_platform/src/app/office/program/detail/list/list.component.scss index b90b4a34e..0271140c3 100644 --- a/projects/social_platform/src/app/office/program/detail/list/list.component.scss +++ b/projects/social_platform/src/app/office/program/detail/list/list.component.scss @@ -7,10 +7,6 @@ gap: 20px; padding-bottom: 100px; - &__search { - flex-grow: 1; - } - &__outlet { display: flex; flex-direction: column;