From 610c15c9fd73a99360460df4c6b2f489a80fe084 Mon Sep 17 00:00:00 2001 From: Sergey Karmanov Date: Mon, 11 May 2026 01:58:09 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BA=D0=B0=D0=B1=D0=B8=D0=BD=D0=B5=D1=82=D1=8B=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B5=D0=BF=D0=BE=D0=B4=D0=B0=D0=B2=D0=B0=D1=82?= =?UTF-8?q?=D0=B5=D0=BB=D1=8F=20=D0=B8=20=D0=B0=D0=B4=D0=BC=D0=B8=D0=BD?= =?UTF-8?q?=D0=B8=D1=81=D1=82=D1=80=D0=B0=D1=82=D0=BE=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/UniVerse.Api/UniVerse.Api.csproj | 1 - frontend/src/api/index.ts | 45 +++++++++ frontend/src/api/types.ts | 47 ++++++++++ frontend/src/router/index.ts | 5 + .../src/views/admin/AdminDashboardView.vue | 45 +++++++-- .../src/views/admin/AdminLLMQueueView.vue | 54 +++++++++-- .../src/views/admin/AdminLecturesView.vue | 94 +++++++++++++++---- frontend/src/views/admin/AdminUsersView.vue | 71 +++++++++++--- .../views/teacher/TeacherAnalyticsView.vue | 49 ++++++---- .../views/teacher/TeacherDashboardView.vue | 42 ++++++--- .../src/views/teacher/TeacherLecturesView.vue | 36 +++++-- 11 files changed, 399 insertions(+), 90 deletions(-) diff --git a/backend/UniVerse.Api/UniVerse.Api.csproj b/backend/UniVerse.Api/UniVerse.Api.csproj index e6b73df..498c052 100644 --- a/backend/UniVerse.Api/UniVerse.Api.csproj +++ b/backend/UniVerse.Api/UniVerse.Api.csproj @@ -17,7 +17,6 @@ - diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 16e7bf9..d7230f8 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -3,12 +3,17 @@ import type { AchievementDto, AuthResponse, CoinTransactionDto, + CourseDto, LectureDto, LectureQuery, + LocationDto, PagedResult, ReviewDto, + SyncStatusDto, + TagDto, UserAchievementDto, UserDto, + UserQuery, UserStatsDto, } from './types' @@ -41,6 +46,12 @@ export const lecturesApi = { export const usersApi = { get: (id: string | number) => apiRequest(`/users/${id}`), + async list(query: UserQuery = {}) { + const payload = await apiRequest | UserDto[]>('/users', { + query: query as Record, + }) + return extractItems(payload) + }, stats: (id: string | number) => apiRequest(`/users/${id}/stats`), async enrollments(id: string | number) { const payload = await apiRequest | LectureDto[] | undefined>(`/users/${id}/enrollments`) @@ -59,6 +70,10 @@ export const usersApi = { ) return extractItems(payload) }, + setRole: (id: string | number, role: 'Student' | 'Teacher' | 'Admin') => + apiRequest(`/users/${id}/role`, { method: 'PATCH', body: JSON.stringify(role) }), + setActive: (id: string | number, isActive: boolean) => + apiRequest(`/users/${id}/active`, { method: 'PATCH', body: JSON.stringify(isActive) }), } export const reviewsApi = { @@ -67,4 +82,34 @@ export const reviewsApi = { method: 'POST', body: JSON.stringify({ lectureId: Number(lectureId), rating, text }), }), + async pending() { + const payload = await apiRequest | ReviewDto[]>('/reviews/pending') + return extractItems(payload) + }, + reanalyze: (id: string | number) => apiRequest(`/reviews/${id}/reanalyze`, { method: 'POST' }), +} + +export const coursesApi = { + async list() { + const payload = await apiRequest | CourseDto[]>('/courses', { query: { PageSize: 100 } }) + return extractItems(payload) + }, +} + +export const locationsApi = { + async list() { + const payload = await apiRequest | LocationDto[]>('/locations') + return extractItems(payload) + }, +} + +export const tagsApi = { + async list() { + const payload = await apiRequest | TagDto[]>('/tags') + return extractItems(payload) + }, +} + +export const syncApi = { + status: () => apiRequest('/sync/status'), } diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts index 2f9ede9..ea6f23c 100644 --- a/frontend/src/api/types.ts +++ b/frontend/src/api/types.ts @@ -44,6 +44,14 @@ export interface UserDto extends UserAuthDto { createdAt: string } +export interface UserQuery { + Search?: string + Role?: ApiUserRole + IsActive?: boolean + Page?: number + PageSize?: number +} + export interface UserStatsDto { totalLectures: number attendedLectures: number @@ -102,6 +110,45 @@ export interface AchievementDto { createdAt: string } +export interface CourseDto { + id: number + name?: string | null + description?: string | null + isSynced: boolean + tags?: TagDto[] | null + createdAt: string +} + +export interface LocationDto { + id: number + name?: string | null + building?: string | null + room?: string | null + address?: string | null + createdAt: string +} + +export type ApiTagType = 'Institute' | 'Faculty' | 'Subject' | 'Organization' | 'Topic' | 'Other' + +export interface TagDto { + id: number + name?: string | null + type: ApiTagType + parentId?: number | null + createdAt: string +} + +export interface SyncStatusDto { + lastSyncAt?: string | null + status?: string | null + lastResult?: { + created: number + updated: number + skipped: number + error?: string | null + } | null +} + export interface UserAchievementDto { id: number achievement: AchievementDto diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 54cb61b..7c22f41 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -45,6 +45,11 @@ router.beforeEach(async (to) => { if (!to.meta.public && !auth.isAuthenticated) { return '/login' } + if (to.meta.role && auth.user && auth.user.role !== to.meta.role) { + if (auth.user.role === 'teacher') return '/teacher' + if (auth.user.role === 'admin') return '/admin' + return '/' + } }) export default router diff --git a/frontend/src/views/admin/AdminDashboardView.vue b/frontend/src/views/admin/AdminDashboardView.vue index 66db735..fd63518 100644 --- a/frontend/src/views/admin/AdminDashboardView.vue +++ b/frontend/src/views/admin/AdminDashboardView.vue @@ -1,8 +1,11 @@