174 lines
5.4 KiB
TypeScript
174 lines
5.4 KiB
TypeScript
import type {
|
|
Achievement,
|
|
CoinTransaction,
|
|
Lecture,
|
|
Notification,
|
|
Review,
|
|
User,
|
|
UserRole,
|
|
} from '@/types'
|
|
import type {
|
|
AchievementDto,
|
|
CoinTransactionDto,
|
|
LectureDto,
|
|
ReviewDto,
|
|
CurrentUserDto,
|
|
UserAuthDto,
|
|
UserDto,
|
|
UserStatsDto,
|
|
UserAchievementDto,
|
|
UserNotificationDto,
|
|
} from './types'
|
|
|
|
export function mapApiRole(role: string | undefined): UserRole {
|
|
if (role === 'Teacher') return 'teacher'
|
|
if (role === 'Admin') return 'admin'
|
|
return 'student'
|
|
}
|
|
|
|
function mapApiRoles(roles: string[] | undefined): UserRole[] {
|
|
if (!roles?.length) return ['student']
|
|
return Array.from(new Set(roles.map(mapApiRole)))
|
|
}
|
|
|
|
function getDefaultActiveRole(roles: UserRole[]): UserRole {
|
|
if (roles.includes('admin')) return 'admin'
|
|
if (roles.includes('teacher')) return 'teacher'
|
|
if (roles.includes('student')) return 'student'
|
|
return 'student'
|
|
}
|
|
|
|
export function mapApiUser(
|
|
user: UserAuthDto | UserDto | CurrentUserDto,
|
|
stats?: UserStatsDto,
|
|
): User {
|
|
const roles = mapApiRoles(user.roles)
|
|
return {
|
|
id: user.id,
|
|
name: user.displayName || user.email || 'Пользователь UniVerse',
|
|
email: user.email || '',
|
|
roles,
|
|
activeRole: getDefaultActiveRole(roles),
|
|
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),
|
|
currentLevelXp: stats?.currentLevelXp ?? 0,
|
|
nextLevelXp: stats?.nextLevelXp,
|
|
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))
|
|
: [],
|
|
activeEnrollments: stats?.activeEnrollments,
|
|
enrollmentSlotLimit: stats?.enrollmentSlotLimit,
|
|
enrollmentSlotRules: stats?.enrollmentSlotRules,
|
|
}
|
|
}
|
|
|
|
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),
|
|
teacherId: lecture.teacherId,
|
|
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: undefined,
|
|
format: lecture.format === 'Online' ? 'online' : 'offline',
|
|
totalSeats,
|
|
enrolledSeats: enrolled,
|
|
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 === 'Positive'
|
|
? 'positive'
|
|
: review.sentiment === 'Negative'
|
|
? 'negative'
|
|
: 'neutral'
|
|
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',
|
|
}
|
|
}
|
|
|
|
export function mapApiNotification(notification: UserNotificationDto): Notification {
|
|
return {
|
|
id: String(notification.id),
|
|
type: notification.type,
|
|
title: notification.title,
|
|
body: notification.body,
|
|
read: notification.isRead,
|
|
createdAt: notification.createdAt,
|
|
}
|
|
}
|