/
Codename-Nik
/
WebApp
Обзор
Документация
Войти
/
Codename-Nik
/
WebApp
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
frontend/src/components/InvitationsList.vue
250 строк
7 KB
Nikolay Pokhodnya
first commit
03 ноя 2025, 19:47
03 ноя 2025, 19:47
a9fbdee
Код
Авторство
О чём код?
<template> <div> <v-card> <v-card-title class="d-flex align-center"> <v-icon icon="mdi-account-plus" class="mr-2"></v-icon> Приглашения в проект </v-card-title> <v-card-text> <div v-if="invitations.length === 0" class="text-center py-8"> <v-icon size="64" color="grey-lighten-2">mdi-email-outline</v-icon> <div class="text-h6 text-grey mt-4">Нет активных приглашений</div> </div> <v-list v-else lines="two"> <v-list-item v-for="invitation in invitations" :key="invitation.id" class="mb-2" > <template v-slot:prepend> <v-avatar color="primary" size="40"> <v-icon icon="mdi-email" color="white"></v-icon> </v-avatar> </template> <v-list-item-title class="font-weight-medium"> Приглашение в проект "{{ invitation.project.name }}" </v-list-item-title> <v-list-item-subtitle> От: {{ invitation.invited_by.username }} • {{ formatDate(invitation.created_at) }} </v-list-item-subtitle> <template v-slot:append> <div class="d-flex gap-2"> <v-btn color="success" size="small" @click="acceptInvitation(invitation.id)" :loading="loadingActions.includes(invitation.id)" > Принять </v-btn> <v-btn color="error" variant="outlined" size="small" @click="showRejectDialog(invitation)" :loading="loadingActions.includes(invitation.id)" > Отклонить </v-btn> </div> </template> </v-list-item> </v-list> </v-card-text> </v-card> <ConfirmDialog v-model="showRejectConfirm" :title="rejectDialogTitle" :message="rejectDialogMessage" confirm-text="Отклонить" icon="mdi-cancel" color="error" :loading="rejectLoading" @confirm="rejectInvitation" @cancel="cancelReject" /> </div> </template> <script setup lang="ts"> import { ref, computed, onMounted } from 'vue' import { useNotificationsStore } from '@/stores/notifications' import ConfirmDialog from '@/components/ConfirmDialog.vue' import api from '@/utils/axios' interface Invitation { id: number project: { id: number name: string } invited_by: { id: number username: string first_name: string last_name: string } created_at: string status: 'pending' | 'accepted' | 'rejected' } const notifications = useNotificationsStore() const invitations = ref<Invitation[]>([]) const loadingActions = ref<number[]>([]) const showRejectConfirm = ref(false) const rejectLoading = ref(false) const currentRejectInvitation = ref<Invitation | null>(null) const response = await api.get('/invitations/') invitations.value = response.data // Заглушка данных - замените на реальный API вызов /*const mockInvitations: Invitation[] = [ { id: 1, project: { id: 1, name: 'Разработка нового функционала' }, invited_by: { id: 2, username: 'alex_dev', first_name: 'Алексей', last_name: 'Петров' }, created_at: '2024-01-15T10:30:00Z', status: 'pending' }, { id: 2, project: { id: 2, name: 'Маркетинговая кампания' }, invited_by: { id: 3, username: 'maria_mkt', first_name: 'Мария', last_name: 'Иванова' }, created_at: '2024-01-14T14:20:00Z', status: 'pending' } ] */ const rejectDialogTitle = computed(() => { return currentRejectInvitation.value ? `Отклонить приглашение в проект "${currentRejectInvitation.value.project.name}"` : 'Отклонить приглашение' }) const rejectDialogMessage = computed(() => { return currentRejectInvitation.value ? `Вы уверены, что хотите отклонить приглашение в проект "${currentRejectInvitation.value.project.name}"?` : 'Вы уверены, что хотите отклонить приглашение?' }) onMounted(() => { loadInvitations() }) const loadInvitations = async () => { try { const response = await api.get('/api/invitations/') invitations.value = response.data // invitations.value = mockInvitations } catch (error) { notifications.error('Не удалось загрузить приглашения') } } const acceptInvitation = async (invitationId: number) => { loadingActions.value.push(invitationId) try { await api.post(`/api/invitations/${invitationId}/accept/`) //await new Promise(resolve => setTimeout(resolve, 1000)) const invitation = invitations.value.find(i => i.id === invitationId) if (invitation) { invitation.status = 'accepted' } notifications.success('Приглашение принято!') setTimeout(() => { invitations.value = invitations.value.filter(i => i.id !== invitationId) }, 1000) } catch (error) { notifications.error('Не удалось принять приглашение') } finally { loadingActions.value = loadingActions.value.filter(id => id !== invitationId) } } const showRejectDialog = (invitation: Invitation) => { currentRejectInvitation.value = invitation showRejectConfirm.value = true } const rejectInvitation = async () => { if (!currentRejectInvitation.value) return const invitationId = currentRejectInvitation.value.id rejectLoading.value = true try { await api.post(`/api/invitations/${invitationId}/reject/`) //await new Promise(resolve => setTimeout(resolve, 1000)) const invitation = invitations.value.find(i => i.id === invitationId) if (invitation) { invitation.status = 'rejected' } notifications.success('Приглашение отклонено') setTimeout(() => { invitations.value = invitations.value.filter(i => i.id !== invitationId) showRejectConfirm.value = false currentRejectInvitation.value = null }, 1000) } catch (error) { notifications.error('Не удалось отклонить приглашение') } finally { rejectLoading.value = false } } const cancelReject = () => { showRejectConfirm.value = false currentRejectInvitation.value = null } const formatDate = (dateString: string) => { return new Date(dateString).toLocaleDateString('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' }) } </script> <style scoped> .gap-2 { gap: 8px; } </style>