Files
UniVerse/frontend/src/views/teacher/TeacherDashboardView.vue
T
serega404 c4ed23a3d9
🚀 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 1m22s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Failing after 26s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 4s
Backend CI / build-and-test (pull_request) Successful in 54s
Frontend CI / build-and-check (pull_request) Failing after 5m4s
refactor: настроил линтер против сиротского css
2026-05-25 02:15:34 +03:00

137 lines
3.9 KiB
Vue

<script setup lang="ts">
import { computed, onMounted, watch } from 'vue'
import { useRouter } from 'vue-router'
import { useLecturesStore } from '@/stores/lectures'
import { useAuthStore } from '@/stores/auth'
import GlassCard from '@/components/ui/GlassCard.vue'
import StatsWidget from '@/components/ui/StatsWidget.vue'
import EmptyState from '@/components/ui/EmptyState.vue'
const lecturesStore = useLecturesStore()
const auth = useAuthStore()
const router = useRouter()
const teacherLectures = computed(() => {
return lecturesStore.all
})
const upcoming = computed(() =>
teacherLectures.value.filter((l) => l.status !== 'completed').slice(0, 3),
)
const enrolledTotal = computed(() =>
teacherLectures.value.reduce((sum, l) => sum + l.enrolledSeats, 0),
)
const visibility = computed(() =>
teacherLectures.value.length ? Math.min(100, Math.round(enrolledTotal.value * 4)) : 0,
)
function fetchTeacherLectures() {
if (!auth.user?.id) return
void lecturesStore.fetchLectures({ TeacherId: auth.user.id })
}
onMounted(fetchTeacherLectures)
watch(() => auth.user?.id, fetchTeacherLectures)
</script>
<template>
<div class="teacher-dashboard page-content">
<div class="header">
<h1 class="page-title">Дашборд преподавателя</h1>
<div class="actions">
<button class="btn-primary" @click="router.push('/teacher/lectures')">Мои лекции</button>
<button class="btn-secondary" @click="router.push('/teacher/analytics')">
Посмотреть отзывы
</button>
</div>
</div>
<div class="stats-row">
<StatsWidget label="Предстоящие лекции" :value="upcoming.length" icon="📅" color="green" />
<StatsWidget label="Записавшихся" :value="enrolledTotal" icon="👥" color="aqua" />
<StatsWidget label="Средняя оценка" :value="'—'" icon="⭐" color="orange" />
<StatsWidget
label="Вовлеченность вне направления"
:value="`${visibility}%`"
icon="🌍"
color="purple"
/>
</div>
<GlassCard>
<div class="section-title">Ближайшие лекции</div>
<EmptyState
v-if="!upcoming.length"
title="Лекций пока нет"
subtitle="После синхронизации или назначения лекции появятся здесь."
/>
<div v-else class="upcoming">
<div class="upcoming-item" v-for="l in upcoming" :key="l.id">
<div>
<div class="upcoming-title">{{ l.title }}</div>
<div class="upcoming-meta">
{{ new Date(l.date).toLocaleDateString('ru-RU') }} {{ l.time }}
</div>
<div class="upcoming-meta">Записалось {{ l.enrolledSeats }} студентов</div>
</div>
<button class="btn-secondary btn-sm" @click="router.push('/teacher/lectures')">
Управлять
</button>
</div>
</div>
</GlassCard>
</div>
</template>
<style scoped>
.teacher-dashboard {
display: flex;
flex-direction: column;
gap: 18px;
}
.header {
display: flex;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
}
.actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.stats-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 16px;
}
.upcoming {
display: flex;
flex-direction: column;
gap: 12px;
margin-top: 10px;
}
.upcoming-item {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
padding-bottom: 10px;
border-bottom: 1px solid var(--color-border-glass);
}
.upcoming-item:last-child {
border-bottom: none;
padding-bottom: 0;
}
.upcoming-title {
font-weight: 700;
}
.upcoming-meta {
font-size: 13px;
color: var(--color-text-secondary);
}
.btn-sm {
padding: 6px 12px;
font-size: 12px;
}
</style>