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

This commit is contained in:
2026-05-11 01:33:38 +03:00
parent 71e7d84e0f
commit 779b6aba77
21 changed files with 942 additions and 365 deletions
+16 -10
View File
@@ -1,12 +1,15 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { computed, onMounted, ref } 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 StatusBadge from '@/components/ui/StatusBadge.vue'
import ModalDialog from '@/components/ui/ModalDialog.vue'
import EmptyState from '@/components/ui/EmptyState.vue'
const lecturesStore = useLecturesStore()
const auth = useAuthStore()
const router = useRouter()
const activeTab = ref<'upcoming' | 'history'>('upcoming')
const cancelModal = ref(false)
@@ -16,19 +19,20 @@ const upcoming = computed(() =>
lecturesStore.registeredLectures.map(l => ({ ...l, status: 'registered' }))
)
const history = ref([
{ id: '1', title: 'Введение в нейронные сети и глубокое обучение', date: '2025-04-20', time: '14:00', building: 'ИКТИБ', room: '305', status: 'attended' },
{ id: '4', title: 'Философия цифровой эпохи', date: '2025-04-12', time: '18:00', building: 'Онлайн', room: '', status: 'needsReview' },
{ id: '5', title: 'Право в информационном обществе', date: '2025-04-05', time: '15:30', building: 'ЮФ', room: '412', status: 'cancelled' },
])
const history = computed(() => lecturesStore.all.filter(l => l.status === 'completed'))
onMounted(async () => {
if (!lecturesStore.all.length) await lecturesStore.fetchLectures()
if (auth.user) await lecturesStore.fetchRegisteredForUser(auth.user.id)
})
function openCancel(id: string) {
selectedId.value = id
cancelModal.value = true
}
function confirmCancel() {
if (selectedId.value) lecturesStore.unregister(selectedId.value)
async function confirmCancel() {
if (selectedId.value) await lecturesStore.unregister(selectedId.value)
cancelModal.value = false
}
</script>
@@ -49,6 +53,7 @@ function confirmCancel() {
</div>
<div v-if="activeTab === 'upcoming'" class="list">
<EmptyState v-if="!upcoming.length" title="Нет предстоящих записей" subtitle="Выберите лекцию в каталоге и запишитесь на неё." />
<GlassCard v-for="item in upcoming" :key="item.id" class="lecture-row">
<div>
<div class="lecture-title">{{ item.title }}</div>
@@ -64,6 +69,7 @@ function confirmCancel() {
</div>
<div v-else class="list">
<EmptyState v-if="!history.length" title="История пока пуста" subtitle="Завершённые лекции появятся здесь после посещения." />
<GlassCard v-for="item in history" :key="item.id" class="lecture-row">
<div>
<div class="lecture-title">{{ item.title }}</div>
@@ -71,8 +77,8 @@ function confirmCancel() {
<div class="lecture-meta">🏛 {{ item.building }} {{ item.room ? `· ауд. ${item.room}` : '' }}</div>
</div>
<div class="lecture-actions">
<StatusBadge :status="item.status" />
<button v-if="item.status === 'needsReview'" class="btn-primary btn-sm" @click="router.push(`/review/${item.id}`)">
<StatusBadge :status="item.status ?? 'completed'" />
<button class="btn-primary btn-sm" @click="router.push(`/review/${item.id}`)">
Оставить отзыв
</button>
</div>