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,11 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import GlassCard from '@/components/ui/GlassCard.vue'
import StatsWidget from '@/components/ui/StatsWidget.vue'
import ProgressBar from '@/components/ui/ProgressBar.vue'
import StatusBadge from '@/components/ui/StatusBadge.vue'
import { lecturesApi, reviewsApi, syncApi, usersApi } from '@/api'
import type { LectureDto, ReviewDto, SyncStatusDto, UserDto } from '@/api/types'
const disciplines = [
{ name: 'Информатика и ИИ', value: 80 },
@@ -10,6 +13,30 @@ const disciplines = [
{ name: 'Философия и этика', value: 42 },
{ name: 'Право и политика', value: 36 },
]
const users = ref<UserDto[]>([])
const lectures = ref<LectureDto[]>([])
const reviews = ref<ReviewDto[]>([])
const syncStatus = ref<SyncStatusDto | null>(null)
const enrollmentCount = computed(() => lectures.value.reduce((sum, lecture) => sum + lecture.enrollmentsCount, 0))
const syncMeta = computed(() =>
syncStatus.value?.lastSyncAt
? `Последняя синхронизация: ${new Date(syncStatus.value.lastSyncAt).toLocaleString('ru-RU')}`
: 'Синхронизация ещё не выполнялась',
)
onMounted(async () => {
const [usersResult, lecturesResult, reviewsResult, syncResult] = await Promise.allSettled([
usersApi.list({ PageSize: 100 }),
lecturesApi.list({ PageSize: 100 }),
reviewsApi.pending(),
syncApi.status(),
])
if (usersResult.status === 'fulfilled') users.value = usersResult.value
if (lecturesResult.status === 'fulfilled') lectures.value = lecturesResult.value
if (reviewsResult.status === 'fulfilled') reviews.value = reviewsResult.value
if (syncResult.status === 'fulfilled') syncStatus.value = syncResult.value
})
</script>
<template>
@@ -17,10 +44,10 @@ const disciplines = [
<h1 class="page-title">Дашборд администратора</h1>
<div class="stats-row">
<StatsWidget label="Пользователей" :value="1247" icon="👥" color="green" />
<StatsWidget label="Лекций" :value="89" icon="📚" color="aqua" />
<StatsWidget label="Записей" :value="3421" icon="🗓️" color="orange" />
<StatsWidget label="Отзывов" :value="1089" icon="💬" color="purple" />
<StatsWidget label="Пользователей" :value="users.length" icon="👥" color="green" />
<StatsWidget label="Лекций" :value="lectures.length" icon="📚" color="aqua" />
<StatsWidget label="Записей" :value="enrollmentCount" icon="🗓️" color="orange" />
<StatsWidget label="Отзывов в LLM" :value="reviews.length" icon="💬" color="purple" />
</div>
<div class="grid">
@@ -54,14 +81,14 @@ const disciplines = [
<div class="grid">
<GlassCard>
<div class="section-title">Состояние синхронизации расписания</div>
<StatusBadge status="open" />
<div class="sync-meta">Последняя синхронизация: сегодня, 09:15</div>
<div class="sync-error">Ошибка: 2 аудитории не сопоставлены с корпусами</div>
<StatusBadge :status="syncStatus?.status ?? 'pending'" />
<div class="sync-meta">{{ syncMeta }}</div>
<div class="sync-error" v-if="syncStatus?.lastResult?.error">Ошибка: {{ syncStatus.lastResult.error }}</div>
</GlassCard>
<GlassCard>
<div class="section-title">Очередь LLM-анализа</div>
<div class="queue-meta">В очереди: 24 отзыва · Обработка: 6/час</div>
<ProgressBar :value="60" :max="100" />
<div class="queue-meta">В очереди: {{ reviews.length }} отзывов</div>
<ProgressBar :value="Math.min(reviews.length * 10, 100)" :max="100" />
<div class="queue-status">Следующая проверка через 12 минут</div>
</GlassCard>
</div>