generated from VLADIMIR/template_frontend
add
This commit is contained in:
@@ -57,6 +57,33 @@ const game = ref<Game>({
|
||||
scenario: undefined,
|
||||
teams: undefined,
|
||||
})
|
||||
const participationFilter = ref('all')
|
||||
const participationOptions = [
|
||||
{ label: 'Все команды', value: 'all' },
|
||||
{ label: 'Ожидает ответа', value: 'pending' },
|
||||
{ label: 'Подтверждено', value: 'confirmed' },
|
||||
{ label: 'Отменено', value: 'cancelled' },
|
||||
]
|
||||
const participationSummary = computed(() =>
|
||||
participationOptions.slice(1).map((option) => {
|
||||
const teams = (game.value.teams || []).filter(
|
||||
(team) => team.participationStatus === option.value,
|
||||
)
|
||||
return {
|
||||
...option,
|
||||
count: teams.length,
|
||||
participants: teams.reduce((sum, team) => sum + (team.participantsCount || 0), 0),
|
||||
}
|
||||
}),
|
||||
)
|
||||
const activeTeamCount = computed(
|
||||
() => (game.value.teams || []).filter((team) => team.participationStatus !== 'cancelled').length,
|
||||
)
|
||||
const visibleTeams = computed(() =>
|
||||
(game.value.teams || []).filter(
|
||||
(team) => participationFilter.value === 'all' || team.participationStatus === participationFilter.value,
|
||||
),
|
||||
)
|
||||
const screens = ref<ScreenState[]>([])
|
||||
const mapItems = ref<MapItem[]>([])
|
||||
const mapSaving = ref(false)
|
||||
@@ -308,6 +335,10 @@ const giveApplicationTeam = ref<Team>({
|
||||
applications: [],
|
||||
captainEmail: undefined,
|
||||
participantsCount: undefined,
|
||||
participationStatus: undefined,
|
||||
participationSource: undefined,
|
||||
participationEmailError: undefined,
|
||||
participationEmailSent: undefined,
|
||||
})
|
||||
|
||||
const deletedTeamName = ref('')
|
||||
@@ -320,6 +351,10 @@ const deletedTeam = ref<Team>({
|
||||
applications: [],
|
||||
captainEmail: undefined,
|
||||
participantsCount: undefined,
|
||||
participationStatus: undefined,
|
||||
participationSource: undefined,
|
||||
participationEmailError: undefined,
|
||||
participationEmailSent: undefined,
|
||||
})
|
||||
|
||||
const giveApplicationApplication = ref<Application>({
|
||||
@@ -570,7 +605,7 @@ useSimplePolling(() => {
|
||||
<div class="settings-block">
|
||||
<n-flex justify="space-between">
|
||||
<n-space>
|
||||
<n-tag :bordered="false" type="info"> Команд: {{ game.teams?.length }} </n-tag>
|
||||
<n-tag :bordered="false" type="info"> Участвуют команд: {{ activeTeamCount }} </n-tag>
|
||||
<n-tag type="info">
|
||||
{{ timeString }}
|
||||
</n-tag>
|
||||
@@ -588,7 +623,7 @@ useSimplePolling(() => {
|
||||
</Icon>
|
||||
Итоги
|
||||
</n-button>
|
||||
<n-button v-if="game.teams?.length" quaternary :loading="isDownloadingQR" :disabled="isDownloadingQR" @click="downloadTeamQR()">
|
||||
<n-button v-if="activeTeamCount" quaternary :loading="isDownloadingQR" :disabled="isDownloadingQR" @click="downloadTeamQR()">
|
||||
QR команд
|
||||
</n-button>
|
||||
<n-button quaternary @click="openSettings()">
|
||||
@@ -705,7 +740,14 @@ useSimplePolling(() => {
|
||||
<p class="place-image-plus">+</p>
|
||||
</div>
|
||||
|
||||
<div v-for="team in game.teams" :key="team.id" class="team-block">
|
||||
<n-space vertical>
|
||||
<n-tag v-for="item in participationSummary" :key="item.value">
|
||||
{{ item.label }}: {{ item.count }} команд · {{ item.participants }} участников
|
||||
</n-tag>
|
||||
<n-select v-model:value="participationFilter" :options="participationOptions" aria-label="Статус участия" />
|
||||
</n-space>
|
||||
|
||||
<div v-for="team in visibleTeams" :key="team.id" class="team-block">
|
||||
<div class="team-content-block">
|
||||
<n-flex justify="space-between">
|
||||
<div class="title-block">
|
||||
@@ -714,14 +756,18 @@ useSimplePolling(() => {
|
||||
<small v-if="team.captainEmail"> — {{ team.captainEmail }}</small>
|
||||
<small v-if="team.participantsCount"> · Участников: {{ team.participantsCount }}</small>
|
||||
</span>
|
||||
<div>Поездки: {{ team.actionsCount }}</div>
|
||||
<div>{{ participationOptions.find((option) => option.value === team.participationStatus)?.label }}<span v-if="team.participationSource === 'automatic'"> · автоматически</span></div>
|
||||
<div v-if="!team.captainEmail">Почта капитана не указана</div>
|
||||
<div v-else-if="team.participationEmailError">Ошибка отправки: {{ team.participationEmailError }}</div>
|
||||
<div v-else-if="team.participationEmailSent">Письмо отправлено</div>
|
||||
<div v-if="team.participationStatus !== 'cancelled'">Поездки: {{ team.actionsCount }}</div>
|
||||
</div>
|
||||
|
||||
<n-flex justify="end">
|
||||
<n-button @click="openQR(getTeamStoryURL(team.id!, team.password!), 'Подсмотреть игру')">
|
||||
<n-button v-if="team.participationStatus !== 'cancelled'" @click="openQR(getTeamStoryURL(team.id!, team.password!), 'Подсмотреть игру')">
|
||||
QR
|
||||
</n-button>
|
||||
<n-button @click="toTeamStory(team.id!, team.password!, true)">
|
||||
<n-button v-if="team.participationStatus !== 'cancelled'" @click="toTeamStory(team.id!, team.password!, true)">
|
||||
Подсмотреть игру
|
||||
</n-button>
|
||||
<n-button type="error" ghost @click="deleteTeam(team)"> Удалить </n-button>
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
<script setup lang="ts">
|
||||
import { NButton, NCard, NSpace, useMessage } from 'naive-ui'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import type { ParticipationRsp } from '@/api/generated/crabs/evening_detective_server'
|
||||
import { buildClient } from '@/api/generated/crabs/evening_detective_server/client'
|
||||
import HeaderMenu from '@/components/HeaderMenu.vue'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { formatGameDateTime } from '@/utils/datetime'
|
||||
|
||||
const route = useRoute()
|
||||
const params = new URLSearchParams(route.hash.slice(1))
|
||||
const token = params.get('token') || ''
|
||||
const answer = ref(params.get('answer') === 'cancelled' ? 'cancelled' : 'confirmed')
|
||||
const client = buildClient()
|
||||
const authStore = useAuthStore()
|
||||
const message = useMessage()
|
||||
const participation = ref<ParticipationRsp>()
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
const res = await client.GetParticipation({ token })
|
||||
if (res.error) {
|
||||
error.value = res.error
|
||||
message.error(res.error)
|
||||
return
|
||||
}
|
||||
participation.value = res
|
||||
} catch {
|
||||
error.value = 'Не удалось загрузить данные. Попробуйте ещё раз.'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
const res = await client.AnswerParticipation({ token, answer: answer.value })
|
||||
if (res.status) participation.value = res
|
||||
if (res.error) {
|
||||
error.value = res.error
|
||||
message.error(res.error)
|
||||
}
|
||||
} catch {
|
||||
error.value = 'Не удалось сохранить ответ. Попробуйте ещё раз.'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<HeaderMenu v-if="authStore.isAuthenticated" />
|
||||
<div class="center-block-custom">
|
||||
<div class="width700">
|
||||
<n-card title="Участие в игре">
|
||||
<p v-if="error" role="alert">{{ error }}</p>
|
||||
<template v-if="participation">
|
||||
<h2>{{ participation.gameName }}</h2>
|
||||
<p>Команда: {{ participation.teamName }}</p>
|
||||
<p>Начало: {{ formatGameDateTime(participation.startAt) }}</p>
|
||||
<p>Участников: {{ participation.participantsCount }}</p>
|
||||
<p v-if="participation.status === 'confirmed'" role="status">
|
||||
Участие подтверждено. Ждём вас на игре! Решение окончательное.
|
||||
</p>
|
||||
<p v-else-if="participation.status === 'cancelled'" role="status">
|
||||
Запись отменена. Решение окончательное.
|
||||
</p>
|
||||
<p v-else-if="participation.closed">
|
||||
Приём ответов закрыт. Ваша запись сохранена со статусом «Ожидает ответа».
|
||||
</p>
|
||||
<template v-else>
|
||||
<p><strong>Решение окончательное: изменить его нельзя.</strong></p>
|
||||
<p>Без ответа запись сохраняется со статусом «Ожидает ответа».</p>
|
||||
<n-space vertical>
|
||||
<n-space>
|
||||
<n-button
|
||||
:disabled="loading"
|
||||
:type="answer === 'confirmed' ? 'primary' : 'default'"
|
||||
:aria-pressed="answer === 'confirmed'"
|
||||
@click="answer = 'confirmed'"
|
||||
>Мы участвуем</n-button
|
||||
>
|
||||
<n-button
|
||||
:disabled="loading"
|
||||
:type="answer === 'cancelled' ? 'error' : 'default'"
|
||||
:aria-pressed="answer === 'cancelled'"
|
||||
@click="answer = 'cancelled'"
|
||||
>Мы не участвуем</n-button
|
||||
>
|
||||
</n-space>
|
||||
<n-button
|
||||
:loading="loading"
|
||||
:disabled="loading"
|
||||
:type="answer === 'cancelled' ? 'error' : 'primary'"
|
||||
@click="submit"
|
||||
>
|
||||
{{ answer === 'confirmed' ? 'Подтверждаю участие' : 'Подтверждаю отмену' }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</template>
|
||||
<n-button v-else :loading="loading" @click="load">{{
|
||||
loading ? 'Загрузка' : 'Повторить'
|
||||
}}</n-button>
|
||||
</n-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user