feat: первое подключение фронтенда
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 8s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 54s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 27s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 6s

This commit is contained in:
2026-05-11 01:33:38 +03:00
parent 71e7d84e0f
commit 779b6aba77
21 changed files with 942 additions and 365 deletions
+115
View File
@@ -0,0 +1,115 @@
import type { Achievement, CoinTransaction, Lecture, Review, User, UserRole } from '@/types'
import type {
AchievementDto,
CoinTransactionDto,
LectureDto,
ReviewDto,
UserAuthDto,
UserDto,
UserStatsDto,
UserAchievementDto,
} from './types'
export function mapApiRole(role: string | undefined): UserRole {
if (role === 'Teacher') return 'teacher'
if (role === 'Admin') return 'admin'
return 'student'
}
export function mapApiUser(user: UserAuthDto | UserDto, stats?: UserStatsDto): User {
return {
id: String(user.id),
name: user.displayName || user.email || 'Пользователь UniVerse',
email: user.email || '',
role: mapApiRole(user.role),
avatar: 'avatarUrl' in user ? user.avatarUrl ?? undefined : undefined,
institute: 'ЮФУ',
department: '',
year: 0,
direction: '',
coins: stats?.coins ?? ('coins' in user ? user.coins : 0),
level: stats?.level ?? ('level' in user ? user.level : 1),
xp: stats?.xp ?? ('xp' in user ? user.xp : 0),
lecturesAttended: stats?.attendedLectures ?? 0,
hoursLearned: stats ? Math.round(stats.attendedLectures * 1.5 * 10) / 10 : 0,
achievements: stats ? Array.from({ length: stats.achievementsCount }, (_, index) => String(index + 1)) : [],
}
}
export function mapApiLecture(lecture: LectureDto): Lecture {
const startsAt = new Date(lecture.startsAt)
const endsAt = new Date(lecture.endsAt)
const durationMs = endsAt.getTime() - startsAt.getTime()
const duration = Number.isFinite(durationMs) && durationMs > 0 ? Math.round(durationMs / 60000) : 90
const totalSeats = lecture.maxEnrollments || 0
const enrolled = lecture.enrollmentsCount || 0
const freeSeats = Math.max(totalSeats - enrolled, 0)
const locationName = lecture.locationName || (lecture.format === 'Online' ? 'Онлайн' : 'Аудитория уточняется')
return {
id: String(lecture.id),
title: lecture.title || lecture.courseName || 'Лекция без названия',
description: lecture.description || 'Описание появится позже.',
teacher: lecture.teacherName || 'Преподаватель уточняется',
teacherTitle: '',
department: '',
institute: lecture.courseName || 'ЮФУ',
date: startsAt.toISOString().slice(0, 10),
time: startsAt.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' }),
duration,
building: lecture.format === 'Online' ? 'Онлайн' : locationName,
room: lecture.format === 'Online' ? undefined : locationName,
format: lecture.format === 'Online' ? 'online' : 'offline',
totalSeats,
freeSeats,
registrationClosed: !lecture.isOpen,
tags: lecture.courseName ? [`#${lecture.courseName}`] : [],
rating: 0,
reviewCount: 0,
status: startsAt.getTime() > Date.now() ? 'upcoming' : 'completed',
registered: lecture.isEnrolled,
}
}
export function mapApiReview(review: ReviewDto): Review {
const sentiment = review.sentiment === 'Negative' ? 'negative' : review.sentiment === 'Neutral' ? 'neutral' : 'positive'
const status =
review.llmStatus === 'Rejected' ? 'rejected' : review.llmStatus === 'Analyzed' ? 'done' : 'pending'
return {
id: String(review.id),
lectureId: String(review.lectureId),
userId: String(review.userId),
userName: review.userName || 'Анонимный отзыв',
text: review.text || '',
sentiment,
createdAt: review.createdAt,
status,
quality: review.qualityScore ?? undefined,
}
}
export function mapApiAchievement(input: AchievementDto | UserAchievementDto): Achievement {
const dto = 'achievement' in input ? input.achievement : input
const awardedAt = 'achievement' in input ? input.awardedAt : undefined
return {
id: String(dto.id),
title: dto.name || 'Достижение',
description: dto.description || dto.condition || '',
icon: dto.iconUrl || '⭐',
unlocked: Boolean(awardedAt),
unlockedAt: awardedAt,
coins: dto.coinReward,
}
}
export function mapApiCoinTransaction(transaction: CoinTransactionDto): CoinTransaction {
return {
id: String(transaction.id),
date: transaction.createdAt.slice(0, 10),
description: transaction.description || transaction.type,
amount: transaction.amount,
type: transaction.amount >= 0 ? 'earned' : 'spent',
}
}