Skip to content

Commit cdc0e40

Browse files
committed
add task-file component, add courses services & module, fix video & img urls & classes
1 parent 550107f commit cdc0e40

79 files changed

Lines changed: 1582 additions & 2094 deletions

File tree

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.resolver.ts

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

33
import { inject } from "@angular/core";
4-
import { TrajectoriesService } from "./trajectories.service";
4+
import { CoursesService } from "./courses.service";
55

66
/**
77
* Резолвер для загрузки списка всех доступных траекторий
@@ -14,7 +14,7 @@ import { TrajectoriesService } from "./trajectories.service";
1414
* @returns Promise/Observable с данными траекторий
1515
*/
1616
export const CoursesResolver = () => {
17-
const trajectoriesService = inject(TrajectoriesService);
17+
const coursesService = inject(CoursesService);
1818

19-
return trajectoriesService.getTrajectories(20, 0);
19+
return coursesService.getCourses();
2020
};

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ export const COURSES_ROUTES: Routes = [
3434
},
3535
{
3636
path: ":courseId",
37-
loadChildren: () =>
38-
import("./detail/trajectory-detail.routes").then(c => c.TRAJECTORY_DETAIL_ROUTES),
37+
loadChildren: () => import("./detail/course-detail.routes").then(c => c.COURSE_DETAIL_ROUTES),
3938
},
4039
];
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/** @format */
2+
3+
import { Injectable } from "@angular/core";
4+
import { ApiService } from "@corelib";
5+
import {
6+
CourseCard,
7+
CourseDetail,
8+
CourseLesson,
9+
CourseStructure,
10+
TaskAnswerResponse,
11+
} from "@office/models/courses.model";
12+
import { Observable } from "rxjs";
13+
14+
/**
15+
* Сервис Курсов
16+
*
17+
* Управляет всеми операциями, связанными с курсами, включая:
18+
* - Отслеживание прогресса пользователя по курсу
19+
*/
20+
@Injectable({
21+
providedIn: "root",
22+
})
23+
export class CoursesService {
24+
private readonly COURSE_URL = "/courses";
25+
26+
constructor(private readonly apiService: ApiService) {}
27+
28+
/**
29+
* Получает доступные курсов
30+
*
31+
* @returns Observable<CourseCard[]> - Список доступных курсов
32+
*/
33+
getCourses(): Observable<CourseCard[]> {
34+
return this.apiService.get<CourseCard[]>(`${this.COURSE_URL}/`);
35+
}
36+
37+
/**
38+
* Получает подробную информацию о конкретном курсе
39+
*
40+
* @param id - Уникальный идентификатор курса
41+
* @returns Observable<CourseDetail> - Полная информация о курсе
42+
*/
43+
getCourseDetail(id: number): Observable<CourseDetail> {
44+
return this.apiService.get<CourseDetail>(`${this.COURSE_URL}/${id}/`);
45+
}
46+
47+
/**
48+
* Получает подробную информацию о структуре курса
49+
*
50+
* @param id - Уникальный идентификатор курса
51+
* @returns Observable<CourseStructure> - Полную структуру курса
52+
*/
53+
getCourseStructure(id: number): Observable<CourseStructure> {
54+
return this.apiService.get<CourseStructure>(`${this.COURSE_URL}/${id}/structure/`);
55+
}
56+
57+
/**
58+
* Получает полную информацию по отдельному уроку внутри курса
59+
*
60+
* @param id - Уникальный идентификатор урока
61+
* @returns Observable<CourseLesson> - Полная информация для урока конкретного
62+
*/
63+
getCourseLesson(id: number): Observable<CourseLesson> {
64+
return this.apiService.get<CourseLesson>(`${this.COURSE_URL}/lessons/${id}/`);
65+
}
66+
67+
/**
68+
*
69+
* @param id - Уникальный идентификатор задачи
70+
* @param answerText - Текст ответа
71+
* @param optionIds - id ответов выбранных
72+
* @param fileIds - id файлов загруженных
73+
* @returns Observable<TaskAnswerResponse> - Информация от прохождении урока
74+
*/
75+
postAnswerQuestion(
76+
id: number,
77+
answerText?: any,
78+
optionIds?: number[],
79+
fileIds?: number[]
80+
): Observable<TaskAnswerResponse> {
81+
return this.apiService.post<TaskAnswerResponse>(`${this.COURSE_URL}/tasks/${id}/answer/`, {
82+
answerText,
83+
optionIds,
84+
fileIds,
85+
});
86+
}
87+
}

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,26 @@
22

33
<div class="detail">
44
<div class="detail__body">
5-
@if(trajectory()) {
5+
@if(course()) {
66
<div class="info detail__section detail__info">
7-
<div class="info__cover"></div>
7+
<img class="info__cover" [src]="course()!.headerCoverUrl" alt="cover" />
88

99
<div class="info__avatar">
1010
<app-avatar
11-
[url]="trajectory()?.avatar!"
11+
[url]="course()!.avatarUrl"
1212
[size]="106"
13-
(click)="redirectDetailInfo(trajectory()!.id)"
13+
(click)="redirectDetailInfo(course()!.id)"
1414
></app-avatar>
1515
@if (!isTaskDetail()) {
16-
<h1 class="info__title text-body-12">{{ trajectory()?.name }}</h1>
16+
<h1 class="info__title text-body-12">{{ course()!.title }}</h1>
1717
}
1818
</div>
1919
</div>
2020

2121
<div class="info__body">
2222
<div class="info__actions">
2323
<app-button
24-
(click)="redirectDetailInfo(isTaskDetail() ? trajectory()?.id : undefined)"
24+
(click)="redirectDetailInfo(isTaskDetail() ? course()!.id : undefined)"
2525
[disabled]="false"
2626
size="big"
2727
customTypographyClass="text-body-12"

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,25 @@ $detail-bar-mb: 12px;
2323

2424
position: relative;
2525
padding: 0;
26-
background-color: transparent;
2726
border: none;
2827
border-radius: $body-slide;
2928

3029
&__cover {
3130
position: relative;
3231
height: 136px;
33-
background: linear-gradient(var(--accent), var(--lime));
3432
border-radius: 15px 15px 0 0;
33+
width: 100%;
34+
35+
img {
36+
position: absolute;
37+
top: 0;
38+
right: 0;
39+
bottom: 0;
40+
left: 0;
41+
width: 100%;
42+
height: 100%;
43+
object-fit: cover;
44+
}
3545
}
3646

3747
&__body {

projects/social_platform/src/app/office/courses/detail/trajectory-detail.component.spec.ts renamed to projects/social_platform/src/app/office/courses/detail/course-detail.component.spec.ts

File renamed without changes.

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

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

33
import { CommonModule } from "@angular/common";
4-
import { Component, DestroyRef, inject, signal, type OnDestroy, type OnInit } from "@angular/core";
4+
import { Component, DestroyRef, inject, signal, OnInit } from "@angular/core";
55
import { ActivatedRoute, NavigationEnd, Router, RouterOutlet } from "@angular/router";
6-
import { filter, map, type Subscription } from "rxjs";
6+
import { filter, map, tap } from "rxjs";
77
import { AvatarComponent } from "@ui/components/avatar/avatar.component";
88
import { ButtonComponent } from "@ui/components";
99
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
10+
import { CourseDetail, CourseStructure } from "@office/models/courses.model";
1011

1112
/**
1213
* Компонент детального просмотра траектории
1314
* Отображает навигационную панель и служит контейнером для дочерних компонентов
1415
* Управляет состоянием выбранной траектории и ID траектории из URL
1516
*/
1617
@Component({
17-
selector: "app-trajectory-detail",
18+
selector: "app-course-detail",
1819
standalone: true,
1920
imports: [CommonModule, RouterOutlet, AvatarComponent, ButtonComponent],
20-
templateUrl: "./trajectory-detail.component.html",
21-
styleUrl: "./trajectory-detail.component.scss",
21+
templateUrl: "./course-detail.component.html",
22+
styleUrl: "./course-detail.component.scss",
2223
})
23-
export class TrajectoryDetailComponent implements OnInit, OnDestroy {
24-
route = inject(ActivatedRoute);
25-
router = inject(Router);
26-
destroyRef = inject(DestroyRef);
24+
export class CourseDetailComponent implements OnInit {
25+
private readonly route = inject(ActivatedRoute);
26+
private readonly router = inject(Router);
27+
private readonly destroyRef = inject(DestroyRef);
2728

28-
subscriptions$: Subscription[] = [];
29+
protected readonly isTaskDetail = signal<boolean>(false);
30+
protected readonly isDisabled = signal<boolean>(false);
2931

30-
trajectory = signal<any | undefined>(undefined);
31-
isDisabled = signal<boolean>(false);
32-
isTaskDetail = signal<boolean>(false);
32+
protected readonly courseModules = signal<CourseStructure["modules"]>([]);
33+
protected readonly course = signal<CourseDetail | undefined>(undefined);
3334

3435
/**
3536
* Инициализация компонента
@@ -39,37 +40,36 @@ export class TrajectoryDetailComponent implements OnInit, OnDestroy {
3940
this.route.data
4041
.pipe(
4142
map(data => data["data"]),
42-
filter(trajectory => !!trajectory)
43+
filter(course => !!course),
44+
takeUntilDestroyed(this.destroyRef)
4345
)
4446
.subscribe({
45-
next: trajectory => {
46-
this.trajectory.set(trajectory[0]);
47+
next: ([course, _]: [CourseDetail, CourseStructure]) => {
48+
this.course.set(course);
49+
50+
if (course.accessType !== "program_members") {
51+
this.isDisabled.set(true);
52+
}
4753
},
4854
});
4955

56+
this.isTaskDetail.set(this.router.url.includes("lesson"));
57+
5058
this.router.events
5159
.pipe(
5260
filter((event): event is NavigationEnd => event instanceof NavigationEnd),
5361
takeUntilDestroyed(this.destroyRef)
5462
)
5563
.subscribe(() => {
56-
this.isTaskDetail.set(this.router.url.includes("task"));
64+
this.isTaskDetail.set(this.router.url.includes("lesson"));
5765
});
5866
}
5967

60-
/**
61-
* Очистка ресурсов при уничтожении компонента
62-
* Отписывается от всех активных подписок
63-
*/
64-
ngOnDestroy(): void {
65-
this.subscriptions$.forEach($ => $.unsubscribe());
66-
}
67-
6868
/**
6969
* Перенаправляет на страницу с информацией в завивисимости от listType
7070
*/
7171
redirectDetailInfo(courseId?: number): void {
72-
if (this.trajectory()) {
72+
if (this.course()) {
7373
this.router.navigateByUrl(`/office/courses/${courseId}`);
7474
} else {
7575
this.router.navigateByUrl("/office/courses/all");
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/** @format */
2+
3+
import { inject } from "@angular/core";
4+
import type { ActivatedRouteSnapshot } from "@angular/router";
5+
import { Router } from "@angular/router";
6+
import { CoursesService } from "../courses.service";
7+
import { forkJoin, tap } from "rxjs";
8+
9+
/**
10+
* Резолвер для получения детальной информации о курсе
11+
* Также проверяет isAvailable — если false, редиректит на список курсов
12+
* @param route - снимок маршрута содержащий параметр courseId
13+
* @returns Observable с данными о курсе
14+
*/
15+
export const CoursesDetailResolver = (route: ActivatedRouteSnapshot) => {
16+
const coursesService = inject(CoursesService);
17+
const router = inject(Router);
18+
const courseId = route.parent?.params["courseId"];
19+
20+
return forkJoin([
21+
coursesService.getCourseDetail(courseId).pipe(
22+
tap(course => {
23+
if (!course.isAvailable) {
24+
router.navigate(["/office/courses/all"]);
25+
}
26+
})
27+
),
28+
coursesService.getCourseStructure(courseId),
29+
]);
30+
};
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/** @format */
2+
3+
import { TrajectoryInfoComponent } from "./info/info.component";
4+
import { CourseDetailComponent } from "./course-detail.component";
5+
import { CoursesDetailResolver } from "./course-detail.resolver";
6+
7+
export const COURSE_DETAIL_ROUTES = [
8+
{
9+
path: "",
10+
component: CourseDetailComponent,
11+
resolve: {
12+
data: CoursesDetailResolver,
13+
},
14+
children: [
15+
{
16+
path: "",
17+
component: TrajectoryInfoComponent,
18+
},
19+
{
20+
path: "lesson",
21+
loadChildren: () => import("../lesson/lesson.routes").then(m => m.LESSON_ROUTES),
22+
},
23+
],
24+
},
25+
];

projects/social_platform/src/app/office/courses/detail/info/guards/trajectory-info.guard.ts

Lines changed: 0 additions & 30 deletions
This file was deleted.

0 commit comments

Comments
 (0)