Skip to content

Commit 082a449

Browse files
authored
Courses program detail mobile (#266)
* add new detail pages for courses & program * add styles for detail pages for courses & program * add mobile version of lesson pages * add styles for lesson pages * fix button & content-container width * fix linter in styles for lessons * fix read-more block for mobile * fix linter for styles * adapted to mobile version projects, members, projects-rating pages for program * add styles for adapted pages for mobile version * add mock cover for mobile * fix linter styles
1 parent 366dd45 commit 082a449

46 files changed

Lines changed: 1283 additions & 843 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

projects/social_platform/src/app/office/courses/courses.service.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/** @format */
22

3-
import { Injectable } from "@angular/core";
3+
import { Injectable, signal } from "@angular/core";
44
import { ApiService } from "@corelib";
55
import {
66
CourseCard,
@@ -23,6 +23,9 @@ import { Observable } from "rxjs";
2323
export class CoursesService {
2424
private readonly COURSE_URL = "/courses";
2525

26+
readonly currentLesson = signal<CourseLesson | null>(null);
27+
readonly courseStructure = signal<CourseStructure | null>(null);
28+
2629
constructor(private readonly apiService: ApiService) {}
2730

2831
/**

projects/social_platform/src/app/office/courses/detail/course-detail.component.html

Lines changed: 83 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,46 +2,96 @@
22

33
<div class="detail">
44
<div class="detail__body">
5-
@if(course()) {
5+
@if (course()) {
66
<div class="info detail__section detail__info">
7-
<img class="info__cover" [src]="course()!.headerCoverUrl" alt="cover" />
8-
9-
<div class="info__avatar">
10-
<app-avatar
11-
[url]="course()!.avatarUrl"
12-
[size]="106"
13-
(click)="redirectDetailInfo(course()!.id)"
14-
></app-avatar>
15-
@if (!isTaskDetail()) {
16-
<h1 class="info__title text-body-12">{{ course()!.title }}</h1>
17-
}
7+
@if (showCover) {
8+
<div class="info__cover">
9+
<img class="info__cover" [src]="course()!.headerCoverUrl" alt="cover" />
10+
11+
<div class="info__avatar">
12+
<app-avatar
13+
[url]="course()!.avatarUrl"
14+
[size]="106"
15+
(click)="redirectDetailInfo(course()!.id)"
16+
></app-avatar>
17+
18+
@if (!isTaskDetail() && !isMobile) {
19+
<h1 class="info__title text-body-12">
20+
{{ course()!.title }}
21+
</h1>
22+
}
23+
</div>
1824
</div>
19-
</div>
25+
}
26+
27+
<div class="info__body">
28+
<div class="info__actions" [class.info__actions--task]="showBackOnly">
29+
@if (showBackOnly) {
30+
<app-button size="small" appearance="outline" (click)="redirectDetailInfo(course()!.id)">
31+
назад
32+
</app-button>
2033

21-
<div class="info__body">
22-
<div class="info__actions">
23-
<app-button
24-
(click)="redirectDetailInfo(isTaskDetail() ? course()!.id : undefined)"
25-
[disabled]="false"
26-
size="big"
27-
customTypographyClass="text-body-12"
28-
appearance="outline"
29-
>{{ isTaskDetail() ? "назад к модулю" : "назад" }}</app-button
30-
>
31-
32-
<app-button
33-
[disabled]="isDisabled()"
34-
[class.info__actions--disabled]="isDisabled()"
35-
size="big"
36-
customTypographyClass="text-body-12"
37-
appearance="outline"
38-
(click)="redirectToProgram()"
39-
>вернуться в программу</app-button
40-
>
34+
<div class="task__meta">
35+
@if (currentLesson()) {
36+
<p class="text-body-14">модуль {{ currentLesson()!.moduleOrder }}</p>
37+
<p class="task__lesson text-body-12">урок {{ lessonOrder }}</p>
38+
}
39+
</div>
40+
41+
<app-button
42+
size="small"
43+
appearance="outline"
44+
(click)="course()!.description ? (isAboutModalOpen = true) : null"
45+
>
46+
о курсе
47+
</app-button>
48+
} @else { @if (showAnalyticsButton) {
49+
<app-button [disabled]="true" size="small" appearance="outline">аналитика</app-button>
50+
} @if (showAboutButton) {
51+
<app-button
52+
size="small"
53+
appearance="outline"
54+
class="info__actions--end"
55+
[disabled]="!course()!.description"
56+
(click)="course()!.description ? (isAboutModalOpen = true) : null"
57+
>
58+
о курсе
59+
</app-button>
60+
}
61+
62+
<app-button
63+
class="info__actions--full"
64+
size="big"
65+
appearance="outline"
66+
(click)="redirectDetailInfo(isTaskDetail() ? course()!.id : undefined)"
67+
>
68+
назад
69+
</app-button>
70+
71+
@if (showProgramButton) {
72+
<app-button
73+
class="info__actions--full"
74+
[disabled]="isDisabled()"
75+
size="big"
76+
appearance="outline"
77+
(click)="redirectToProgram()"
78+
>
79+
вернуться в программу
80+
</app-button>
81+
} }
82+
</div>
4183
</div>
4284
</div>
4385

4486
<router-outlet></router-outlet>
87+
88+
<app-modal
89+
[open]="isAboutModalOpen"
90+
(openChange)="isAboutModalOpen = !isAboutModalOpen"
91+
bodyClass="modal__body--no-flex"
92+
>
93+
<app-course-about [description]="course()!.description"></app-course-about>
94+
</app-modal>
4595
}
4696
</div>
4797
</div>

projects/social_platform/src/app/office/courses/detail/course-detail.component.scss

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,16 @@ $detail-bar-mb: 12px;
77
.detail {
88
display: flex;
99
flex-direction: column;
10-
height: 100%;
1110
max-height: 100%;
1211
padding-top: 20px;
12+
padding-bottom: 20px;
13+
margin: 0 20px;
14+
15+
@include responsive.apply-desktop {
16+
height: 100%;
17+
padding-bottom: 0;
18+
margin: 0;
19+
}
1320

1421
&__body {
1522
flex-grow: 1;
@@ -23,14 +30,19 @@ $detail-bar-mb: 12px;
2330

2431
position: relative;
2532
padding: 0;
33+
margin-bottom: 20px;
2634
border: none;
2735
border-radius: $body-slide;
2836

2937
&__cover {
3038
position: relative;
31-
width: 100%;
3239
height: 136px;
33-
border-radius: 15px 15px 0 0;
40+
margin-bottom: 24px;
41+
border-radius: 15px;
42+
43+
@include responsive.apply-desktop {
44+
border-radius: 15px 15px 0 0;
45+
}
3446

3547
img {
3648
position: absolute;
@@ -53,13 +65,14 @@ $detail-bar-mb: 12px;
5365
position: absolute;
5466
bottom: -10px;
5567
left: 50%;
56-
z-index: 100;
68+
z-index: 10;
5769
display: block;
5870
display: flex;
5971
flex-direction: column;
6072
align-items: center;
6173
cursor: pointer;
6274
border-radius: 50%;
75+
transform: translate(-50%, 30%);
6376

6477
&--program {
6578
bottom: 15px;
@@ -102,13 +115,47 @@ $detail-bar-mb: 12px;
102115
&__actions {
103116
display: grid;
104117
grid-template-columns: 1fr 1fr;
105-
gap: 180px;
118+
gap: 12px;
106119
align-items: center;
107-
padding: 24px 0 30px;
120+
121+
@include responsive.apply-desktop {
122+
gap: 180px;
123+
padding: 0 0 30px;
124+
}
125+
126+
&--task {
127+
display: flex;
128+
flex-direction: row-reverse;
129+
align-items: center;
130+
justify-content: space-between;
131+
}
132+
133+
&--end {
134+
justify-self: end;
135+
}
136+
137+
&--full {
138+
grid-column: 1 / -1;
139+
140+
@include responsive.apply-desktop {
141+
grid-column: auto;
142+
}
143+
}
108144

109145
&--disabled {
110146
cursor: not-allowed;
111-
opacity: 0.5;
112147
}
113148
}
114149
}
150+
151+
.task {
152+
&__lesson {
153+
color: var(--dark-grey);
154+
}
155+
156+
&__meta {
157+
display: flex;
158+
flex-direction: column;
159+
align-items: center;
160+
}
161+
}

projects/social_platform/src/app/office/courses/detail/course-detail.component.ts

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
/** @format */
22

33
import { CommonModule } from "@angular/common";
4-
import { Component, DestroyRef, inject, signal, OnInit } from "@angular/core";
4+
import { Component, DestroyRef, HostListener, inject, signal, OnInit } from "@angular/core";
55
import { ActivatedRoute, NavigationEnd, Router, RouterOutlet } from "@angular/router";
66
import { filter, map, tap } from "rxjs";
77
import { AvatarComponent } from "@ui/components/avatar/avatar.component";
88
import { ButtonComponent } from "@ui/components";
9+
import { IconComponent } from "@uilib";
910
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
10-
import { CourseDetail, CourseStructure } from "@office/models/courses.model";
11+
import { CourseDetail, CourseLesson, CourseStructure } from "@office/models/courses.model";
12+
import { ModalComponent } from "@ui/components/modal/modal.component";
13+
import { CourseAboutComponent } from "@office/courses/shared/course-about/course-about.component";
14+
import { CoursesService } from "@office/courses/courses.service";
1115

1216
/**
1317
* Компонент детального просмотра траектории
@@ -17,20 +21,75 @@ import { CourseDetail, CourseStructure } from "@office/models/courses.model";
1721
@Component({
1822
selector: "app-course-detail",
1923
standalone: true,
20-
imports: [CommonModule, RouterOutlet, AvatarComponent, ButtonComponent],
24+
imports: [
25+
CommonModule,
26+
RouterOutlet,
27+
AvatarComponent,
28+
ButtonComponent,
29+
IconComponent,
30+
ModalComponent,
31+
CourseAboutComponent,
32+
],
2133
templateUrl: "./course-detail.component.html",
2234
styleUrl: "./course-detail.component.scss",
2335
})
2436
export class CourseDetailComponent implements OnInit {
2537
private readonly route = inject(ActivatedRoute);
2638
private readonly router = inject(Router);
2739
private readonly destroyRef = inject(DestroyRef);
40+
private readonly coursesService = inject(CoursesService);
41+
42+
appWidth = window.innerWidth;
43+
44+
@HostListener("window:resize")
45+
onResize() {
46+
this.appWidth = window.innerWidth;
47+
}
2848

2949
protected readonly isTaskDetail = signal<boolean>(false);
3050
protected readonly isDisabled = signal<boolean>(false);
51+
isAboutModalOpen = false;
3152

3253
protected readonly courseModules = signal<CourseStructure["modules"]>([]);
3354
protected readonly course = signal<CourseDetail | undefined>(undefined);
55+
protected readonly courseStructure = signal<CourseStructure | undefined>(undefined);
56+
protected readonly currentLesson = this.coursesService.currentLesson;
57+
58+
get lessonOrder(): number | null {
59+
const lesson = this.currentLesson();
60+
const structure = this.courseStructure();
61+
if (!lesson || !structure) return null;
62+
63+
for (const mod of structure.modules) {
64+
const found = mod.lessons.find(l => l.id === lesson.id);
65+
if (found) return found.order;
66+
}
67+
return null;
68+
}
69+
70+
get isMobile(): boolean {
71+
return this.appWidth < 920;
72+
}
73+
74+
get showCover(): boolean {
75+
return !this.isTaskDetail() || !this.isMobile;
76+
}
77+
78+
get showAboutButton(): boolean {
79+
return this.isMobile && !this.isTaskDetail();
80+
}
81+
82+
get showBackOnly(): boolean {
83+
return this.isTaskDetail() && this.isMobile;
84+
}
85+
86+
get showAnalyticsButton(): boolean {
87+
return this.isMobile && !this.isTaskDetail();
88+
}
89+
90+
get showProgramButton(): boolean {
91+
return !this.showBackOnly;
92+
}
3493

3594
/**
3695
* Инициализация компонента
@@ -44,8 +103,10 @@ export class CourseDetailComponent implements OnInit {
44103
takeUntilDestroyed(this.destroyRef)
45104
)
46105
.subscribe({
47-
next: ([course, _]: [CourseDetail, CourseStructure]) => {
106+
next: ([course, structure]: [CourseDetail, CourseStructure]) => {
48107
this.course.set(course);
108+
this.courseStructure.set(structure);
109+
this.coursesService.courseStructure.set(structure);
49110

50111
if (!course.partnerProgramId) {
51112
this.isDisabled.set(true);

projects/social_platform/src/app/office/courses/detail/course-detail.routes.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
/** @format */
22

33
import type { Routes } from "@angular/router";
4-
import { TrajectoryInfoComponent } from "./info/info.component";
54
import { CourseDetailComponent } from "./course-detail.component";
65
import { CoursesDetailResolver } from "./course-detail.resolver";
6+
import { CourseInfoComponent } from "./info/info.component";
77

88
export const COURSE_DETAIL_ROUTES: Routes = [
99
{
@@ -16,7 +16,7 @@ export const COURSE_DETAIL_ROUTES: Routes = [
1616
children: [
1717
{
1818
path: "",
19-
component: TrajectoryInfoComponent,
19+
component: CourseInfoComponent,
2020
},
2121
{
2222
path: "lesson",

0 commit comments

Comments
 (0)