146 lines
4.0 KiB
Vue
146 lines
4.0 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;
|
|
}
|
|
.visibility {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
.visibility-meta {
|
|
font-size: 13px;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
.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>
|