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 38s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 18s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 6s

This commit is contained in:
2026-05-11 01:58:09 +03:00
parent 779b6aba77
commit 610c15c9fd
11 changed files with 399 additions and 90 deletions
@@ -1,8 +1,29 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import GlassCard from '@/components/ui/GlassCard.vue'
import ProgressBar from '@/components/ui/ProgressBar.vue'
import EmptyState from '@/components/ui/EmptyState.vue'
import { lecturesApi } from '@/api'
import type { Review } from '@/types'
import { mapApiReview } from '@/api/mappers'
import { useLecturesStore } from '@/stores/lectures'
const ratingTrend = [4.2, 4.5, 4.6, 4.8, 4.7]
const lecturesStore = useLecturesStore()
const reviews = ref<Review[]>([])
const positive = computed(() => reviews.value.filter(r => r.sentiment === 'positive').length)
const neutral = computed(() => reviews.value.filter(r => r.sentiment === 'neutral').length)
const negative = computed(() => reviews.value.filter(r => r.sentiment === 'negative').length)
const total = computed(() => reviews.value.length || 1)
const pct = (value: number) => Math.round((value / total.value) * 100)
onMounted(async () => {
if (!lecturesStore.all.length) await lecturesStore.fetchLectures()
const targetLectures = lecturesStore.all.slice(0, 5)
const payload = await Promise.allSettled(targetLectures.map(l => lecturesApi.reviews(l.id)))
reviews.value = payload.flatMap(result => (result.status === 'fulfilled' ? result.value.map(mapApiReview) : []))
})
</script>
<template>
@@ -25,16 +46,16 @@ const ratingTrend = [4.2, 4.5, 4.6, 4.8, 4.7]
<div class="section-title">Sentiment-анализ отзывов</div>
<div class="sentiment">
<div>
<div class="sentiment-label">Позитивные 65%</div>
<ProgressBar :value="65" :max="100" />
<div class="sentiment-label">Позитивные {{ pct(positive) }}%</div>
<ProgressBar :value="pct(positive)" :max="100" />
</div>
<div>
<div class="sentiment-label">Нейтральные 25%</div>
<ProgressBar :value="25" :max="100" color="linear-gradient(90deg, #7DD3FC, #BAE6FD)" />
<div class="sentiment-label">Нейтральные {{ pct(neutral) }}%</div>
<ProgressBar :value="pct(neutral)" :max="100" color="linear-gradient(90deg, #7DD3FC, #BAE6FD)" />
</div>
<div>
<div class="sentiment-label">Негативные 10%</div>
<ProgressBar :value="10" :max="100" color="linear-gradient(90deg, #FCA5A5, #FECACA)" />
<div class="sentiment-label">Негативные {{ pct(negative) }}%</div>
<ProgressBar :value="pct(negative)" :max="100" color="linear-gradient(90deg, #FCA5A5, #FECACA)" />
</div>
</div>
</GlassCard>
@@ -56,21 +77,15 @@ const ratingTrend = [4.2, 4.5, 4.6, 4.8, 4.7]
<GlassCard>
<div class="section-title">Анонимные отзывы</div>
<div class="reviews">
<div class="review">
«Больше кейсов и примеров из реальной жизни, лекция очень понравилась»
</div>
<div class="review">
«Темп быстрый, но структура отличная. Хотелось бы больше практических заданий.»
</div>
<div class="review">
«Отличные слайды и примеры, спасибо за доступное объяснение сложных тем.»
<EmptyState v-if="!reviews.length" title="Отзывов пока нет" subtitle="Когда студенты оставят отзывы, они появятся здесь." />
<div v-else class="reviews">
<div v-for="review in reviews" :key="review.id" class="review">
«{{ review.text }}»
</div>
</div>
<div class="section-title">Топ полезных отзывов</div>
<ul class="top-list">
<li>«Лабораторная часть помогла понять алгоритмы, пожалуйста, добавьте еще 15 минут»</li>
<li>«Понравились интерактивные задания, хочется больше времени на Q&A»</li>
<li v-for="review in reviews.slice(0, 2)" :key="review.id">«{{ review.text }}»</li>
</ul>
</GlassCard>
</div>