feat: добавил ограничение записи на лекции
Backend CI / build-and-test (push) Failing after 32s
Frontend CI / build-and-check (push) Failing after 5m5s
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 6s
🚀 Create and publish a Docker image / Build & publish backend image (push) Failing after 1m28s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Failing after 19s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Has been skipped

This commit is contained in:
2026-05-21 19:34:08 +03:00
parent 32b8bdfd24
commit 2e7ce6c2e8
21 changed files with 569 additions and 23 deletions
+10
View File
@@ -3,6 +3,7 @@ import { computed, ref } from 'vue'
import { lecturesApi, usersApi } from '@/api'
import { mapApiLecture, mapApiReview } from '@/api/mappers'
import type { Lecture, Review } from '@/types'
import { useUserStore } from './user'
export const useLecturesStore = defineStore('lectures', () => {
const lectures = ref<Lecture[]>([])
@@ -74,16 +75,23 @@ export const useLecturesStore = defineStore('lectures', () => {
async function register(lectureId: string) {
const lecture = lectures.value.find(item => item.id === lectureId)
if (!lecture || lecture.freeSeats === 0 || lecture.registrationClosed || registered.value.includes(lectureId)) return
const userStore = useUserStore()
if (!userStore.hasEnrollmentSlotAvailable) {
throw new Error('Лимит записей достигнут. Отмените одну из записей или повысьте уровень.')
}
await lecturesApi.enroll(lectureId)
registered.value.push(lectureId)
lecture.freeSeats = Math.max(lecture.freeSeats - 1, 0)
lecture.enrolledSeats += 1
lecture.registered = true
userStore.adjustActiveEnrollments(1)
await userStore.fetchStats().catch(() => undefined)
}
async function unregister(lectureId: string) {
await lecturesApi.unenroll(lectureId)
const userStore = useUserStore()
registered.value = registered.value.filter(id => id !== lectureId)
const lecture = lectures.value.find(item => item.id === lectureId)
if (lecture) {
@@ -91,6 +99,8 @@ export const useLecturesStore = defineStore('lectures', () => {
lecture.enrolledSeats = Math.max(lecture.enrolledSeats - 1, 0)
lecture.registered = false
}
userStore.adjustActiveEnrollments(-1)
await userStore.fetchStats().catch(() => undefined)
}
function isRegistered(lectureId: string) {
+55 -14
View File
@@ -1,7 +1,8 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { computed, ref } from 'vue'
import { achievementsApi, notificationsApi, usersApi } from '@/api'
import { mapApiAchievement, mapApiCoinTransaction, mapApiNotification } from '@/api/mappers'
import type { UserStatsDto } from '@/api/types'
import type { Achievement, CoinTransaction, Notification } from '@/types'
import { useAuthStore } from './auth'
@@ -11,6 +12,53 @@ export const useUserStore = defineStore('user', () => {
const coinHistory = ref<CoinTransaction[]>([])
const loading = ref(false)
const error = ref<string | null>(null)
const activeEnrollments = computed(() => useAuthStore().user?.activeEnrollments ?? 0)
const enrollmentSlotLimit = computed(() => useAuthStore().user?.enrollmentSlotLimit ?? 0)
const hasEnrollmentSlotAvailable = computed(() =>
enrollmentSlotLimit.value === 0 || activeEnrollments.value < enrollmentSlotLimit.value
)
function applyStats(stats: UserStatsDto) {
const auth = useAuthStore()
if (!auth.user) return
auth.setUser({
...auth.user,
coins: stats.coins,
level: stats.level,
xp: stats.xp,
currentLevelXp: stats.currentLevelXp,
nextLevelXp: stats.nextLevelXp,
lecturesAttended: stats.attendedLectures,
hoursLearned: Math.round(stats.attendedLectures * 1.5 * 10) / 10,
achievements: Array.from({ length: stats.achievementsCount }, (_, index) => String(index + 1)),
activeEnrollments: stats.activeEnrollments,
enrollmentSlotLimit: stats.enrollmentSlotLimit,
enrollmentSlotRules: stats.enrollmentSlotRules,
})
}
async function fetchStats() {
error.value = null
try {
const stats = await usersApi.myStats()
applyStats(stats)
return stats
} catch (err) {
error.value = err instanceof Error ? err.message : 'Не удалось загрузить статистику профиля.'
throw err
}
}
function adjustActiveEnrollments(delta: number) {
const auth = useAuthStore()
if (!auth.user || typeof auth.user.activeEnrollments !== 'number') return
auth.setUser({
...auth.user,
activeEnrollments: Math.max(auth.user.activeEnrollments + delta, 0),
})
}
async function fetchStudentData() {
const auth = useAuthStore()
@@ -29,19 +77,7 @@ export const useUserStore = defineStore('user', () => {
notificationsApi.list(),
])
if (auth.user) {
auth.setUser({
...auth.user,
coins: stats.coins,
level: stats.level,
xp: stats.xp,
currentLevelXp: stats.currentLevelXp,
nextLevelXp: stats.nextLevelXp,
lecturesAttended: stats.attendedLectures,
hoursLearned: Math.round(stats.attendedLectures * 1.5 * 10) / 10,
achievements: Array.from({ length: stats.achievementsCount }, (_, index) => String(index + 1)),
})
}
applyStats(stats)
const unlocked = new Map(achievementPayload.map(item => {
const achievement = mapApiAchievement(item)
return [achievement.id, achievement]
@@ -85,6 +121,11 @@ export const useUserStore = defineStore('user', () => {
coinHistory,
loading,
error,
activeEnrollments,
enrollmentSlotLimit,
hasEnrollmentSlotAvailable,
fetchStats,
adjustActiveEnrollments,
fetchStudentData,
fetchNotifications,
markAllRead,