Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** @format */

import { Injectable } from "@angular/core";
import { Injectable, signal } from "@angular/core";
import { ApiService } from "@corelib";
import {
CourseCard,
Expand All @@ -23,6 +23,9 @@ import { Observable } from "rxjs";
export class CoursesService {
private readonly COURSE_URL = "/courses";

readonly currentLesson = signal<CourseLesson | null>(null);
readonly courseStructure = signal<CourseStructure | null>(null);

constructor(private readonly apiService: ApiService) {}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,96 @@

<div class="detail">
<div class="detail__body">
@if(course()) {
@if (course()) {
<div class="info detail__section detail__info">
<img class="info__cover" [src]="course()!.headerCoverUrl" alt="cover" />

<div class="info__avatar">
<app-avatar
[url]="course()!.avatarUrl"
[size]="106"
(click)="redirectDetailInfo(course()!.id)"
></app-avatar>
@if (!isTaskDetail()) {
<h1 class="info__title text-body-12">{{ course()!.title }}</h1>
}
@if (showCover) {
<div class="info__cover">
<img class="info__cover" [src]="course()!.headerCoverUrl" alt="cover" />

<div class="info__avatar">
<app-avatar
[url]="course()!.avatarUrl"
[size]="106"
(click)="redirectDetailInfo(course()!.id)"
></app-avatar>

@if (!isTaskDetail() && !isMobile) {
<h1 class="info__title text-body-12">
{{ course()!.title }}
</h1>
}
</div>
</div>
</div>
}

<div class="info__body">
<div class="info__actions" [class.info__actions--task]="showBackOnly">
@if (showBackOnly) {
<app-button size="small" appearance="outline" (click)="redirectDetailInfo(course()!.id)">
назад
</app-button>

<div class="info__body">
<div class="info__actions">
<app-button
(click)="redirectDetailInfo(isTaskDetail() ? course()!.id : undefined)"
[disabled]="false"
size="big"
customTypographyClass="text-body-12"
appearance="outline"
>{{ isTaskDetail() ? "назад к модулю" : "назад" }}</app-button
>

<app-button
[disabled]="isDisabled()"
[class.info__actions--disabled]="isDisabled()"
size="big"
customTypographyClass="text-body-12"
appearance="outline"
(click)="redirectToProgram()"
>вернуться в программу</app-button
>
<div class="task__meta">
@if (currentLesson()) {
<p class="text-body-14">модуль {{ currentLesson()!.moduleOrder }}</p>
<p class="task__lesson text-body-12">урок {{ lessonOrder }}</p>
}
</div>

<app-button
size="small"
appearance="outline"
(click)="course()!.description ? (isAboutModalOpen = true) : null"
>
о курсе
</app-button>
} @else { @if (showAnalyticsButton) {
<app-button [disabled]="true" size="small" appearance="outline">аналитика</app-button>
} @if (showAboutButton) {
<app-button
size="small"
appearance="outline"
class="info__actions--end"
[disabled]="!course()!.description"
(click)="course()!.description ? (isAboutModalOpen = true) : null"
>
о курсе
</app-button>
}

<app-button
class="info__actions--full"
size="big"
appearance="outline"
(click)="redirectDetailInfo(isTaskDetail() ? course()!.id : undefined)"
>
назад
</app-button>

@if (showProgramButton) {
<app-button
class="info__actions--full"
[disabled]="isDisabled()"
size="big"
appearance="outline"
(click)="redirectToProgram()"
>
вернуться в программу
</app-button>
} }
</div>
</div>
</div>

<router-outlet></router-outlet>

<app-modal
[open]="isAboutModalOpen"
(openChange)="isAboutModalOpen = !isAboutModalOpen"
bodyClass="modal__body--no-flex"
>
<app-course-about [description]="course()!.description"></app-course-about>
</app-modal>
}
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,16 @@ $detail-bar-mb: 12px;
.detail {
display: flex;
flex-direction: column;
height: 100%;
max-height: 100%;
padding-top: 20px;
padding-bottom: 20px;
margin: 0 20px;

@include responsive.apply-desktop {
height: 100%;
padding-bottom: 0;
margin: 0;
}

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

position: relative;
padding: 0;
margin-bottom: 20px;
border: none;
border-radius: $body-slide;

&__cover {
position: relative;
width: 100%;
height: 136px;
border-radius: 15px 15px 0 0;
margin-bottom: 24px;
border-radius: 15px;

@include responsive.apply-desktop {
border-radius: 15px 15px 0 0;
}

img {
position: absolute;
Expand All @@ -53,13 +65,14 @@ $detail-bar-mb: 12px;
position: absolute;
bottom: -10px;
left: 50%;
z-index: 100;
z-index: 10;
display: block;
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
border-radius: 50%;
transform: translate(-50%, 30%);

&--program {
bottom: 15px;
Expand Down Expand Up @@ -102,13 +115,47 @@ $detail-bar-mb: 12px;
&__actions {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 180px;
gap: 12px;
align-items: center;
padding: 24px 0 30px;

@include responsive.apply-desktop {
gap: 180px;
padding: 0 0 30px;
}

&--task {
display: flex;
flex-direction: row-reverse;
align-items: center;
justify-content: space-between;
}

&--end {
justify-self: end;
}

&--full {
grid-column: 1 / -1;

@include responsive.apply-desktop {
grid-column: auto;
}
}

&--disabled {
cursor: not-allowed;
opacity: 0.5;
}
}
}

.task {
&__lesson {
color: var(--dark-grey);
}

&__meta {
display: flex;
flex-direction: column;
align-items: center;
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
/** @format */

import { CommonModule } from "@angular/common";
import { Component, DestroyRef, inject, signal, OnInit } from "@angular/core";
import { Component, DestroyRef, HostListener, inject, signal, OnInit } from "@angular/core";
import { ActivatedRoute, NavigationEnd, Router, RouterOutlet } from "@angular/router";
import { filter, map, tap } from "rxjs";
import { AvatarComponent } from "@ui/components/avatar/avatar.component";
import { ButtonComponent } from "@ui/components";
import { IconComponent } from "@uilib";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { CourseDetail, CourseStructure } from "@office/models/courses.model";
import { CourseDetail, CourseLesson, CourseStructure } from "@office/models/courses.model";
import { ModalComponent } from "@ui/components/modal/modal.component";
import { CourseAboutComponent } from "@office/courses/shared/course-about/course-about.component";
import { CoursesService } from "@office/courses/courses.service";

/**
* Компонент детального просмотра траектории
Expand All @@ -17,20 +21,75 @@ import { CourseDetail, CourseStructure } from "@office/models/courses.model";
@Component({
selector: "app-course-detail",
standalone: true,
imports: [CommonModule, RouterOutlet, AvatarComponent, ButtonComponent],
imports: [
CommonModule,
RouterOutlet,
AvatarComponent,
ButtonComponent,
IconComponent,
ModalComponent,
CourseAboutComponent,
],
templateUrl: "./course-detail.component.html",
styleUrl: "./course-detail.component.scss",
})
export class CourseDetailComponent implements OnInit {
private readonly route = inject(ActivatedRoute);
private readonly router = inject(Router);
private readonly destroyRef = inject(DestroyRef);
private readonly coursesService = inject(CoursesService);

appWidth = window.innerWidth;

@HostListener("window:resize")
onResize() {
this.appWidth = window.innerWidth;
}

protected readonly isTaskDetail = signal<boolean>(false);
protected readonly isDisabled = signal<boolean>(false);
isAboutModalOpen = false;

protected readonly courseModules = signal<CourseStructure["modules"]>([]);
protected readonly course = signal<CourseDetail | undefined>(undefined);
protected readonly courseStructure = signal<CourseStructure | undefined>(undefined);
protected readonly currentLesson = this.coursesService.currentLesson;

get lessonOrder(): number | null {
const lesson = this.currentLesson();
const structure = this.courseStructure();
if (!lesson || !structure) return null;

for (const mod of structure.modules) {
const found = mod.lessons.find(l => l.id === lesson.id);
if (found) return found.order;
}
return null;
}

get isMobile(): boolean {
return this.appWidth < 920;
}

get showCover(): boolean {
return !this.isTaskDetail() || !this.isMobile;
}

get showAboutButton(): boolean {
return this.isMobile && !this.isTaskDetail();
}

get showBackOnly(): boolean {
return this.isTaskDetail() && this.isMobile;
}

get showAnalyticsButton(): boolean {
return this.isMobile && !this.isTaskDetail();
}

get showProgramButton(): boolean {
return !this.showBackOnly;
}

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

if (!course.partnerProgramId) {
this.isDisabled.set(true);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/** @format */

import type { Routes } from "@angular/router";
import { TrajectoryInfoComponent } from "./info/info.component";
import { CourseDetailComponent } from "./course-detail.component";
import { CoursesDetailResolver } from "./course-detail.resolver";
import { CourseInfoComponent } from "./info/info.component";

export const COURSE_DETAIL_ROUTES: Routes = [
{
Expand All @@ -16,7 +16,7 @@ export const COURSE_DETAIL_ROUTES: Routes = [
children: [
{
path: "",
component: TrajectoryInfoComponent,
component: CourseInfoComponent,
},
{
path: "lesson",
Expand Down
Loading
Loading