reports/frontend/src/App.vue

152 lines
3.2 KiB
Vue
Raw Normal View History

2026-06-19 17:23:25 +03:00
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { fetchTeams } from './api'
import type { Team } from './types'
import TeamSidebar from './components/TeamSidebar.vue'
import SprintTable from './components/SprintTable.vue'
const teams = ref<Team[]>([])
const selectedTeamId = ref<number | null>(null)
const loadingTeams = ref(true)
const teamsError = ref<string | null>(null)
onMounted(async () => {
try {
teams.value = await fetchTeams()
if (teams.value.length > 0) {
selectedTeamId.value = teams.value[0].id
}
} catch (error) {
teamsError.value = error instanceof Error ? error.message : 'Не удалось загрузить команды'
} finally {
loadingTeams.value = false
}
})
function selectTeam(teamId: number) {
selectedTeamId.value = teamId
}
const selectedTeam = computed(
() => teams.value.find((team) => team.id === selectedTeamId.value) ?? null,
)
</script>
<template>
<div class="layout">
<aside class="sidebar">
<div class="sidebar__header">
<h1 class="sidebar__title">Команды</h1>
</div>
<div v-if="loadingTeams" class="sidebar__state">Загрузка...</div>
<div v-else-if="teamsError" class="sidebar__state sidebar__state--error">{{ teamsError }}</div>
<TeamSidebar
v-else
:teams="teams"
:selected-team-id="selectedTeamId"
@select="selectTeam"
/>
</aside>
<main class="main">
<header class="main__header">
<h2 class="main__title">
{{ selectedTeam?.name ?? 'Сводка по спринтам' }}
</h2>
<p class="main__subtitle">Спринты отсортированы по дате начала</p>
</header>
<SprintTable v-if="selectedTeamId !== null" :team-id="selectedTeamId" />
<div v-else class="main__empty">Выберите команду в сайдбаре</div>
</main>
</div>
</template>
<style scoped>
.layout {
display: grid;
grid-template-columns: 240px 1fr;
min-height: 100vh;
background: #23272a;
}
.sidebar {
position: relative;
display: flex;
flex-direction: column;
padding: 8px 0 16px;
background: #1e2226;
border-right: 0.5px solid rgba(255, 255, 255, 0.06);
}
.sidebar__header {
padding: 12px 16px 4px;
}
.sidebar__title {
margin: 0;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
color: rgba(235, 235, 245, 0.55);
}
.sidebar__state {
padding: 8px 16px;
font-size: 13px;
color: rgba(235, 235, 245, 0.45);
}
.sidebar__state--error {
color: #ff6961;
}
.main {
padding: 28px 36px;
overflow-x: auto;
background: #23272a;
}
.main__header {
margin-bottom: 24px;
}
.main__title {
margin: 0 0 4px;
font-size: 1.625rem;
font-weight: 600;
letter-spacing: -0.02em;
color: #f5f5f7;
}
.main__subtitle {
margin: 0;
color: rgba(235, 235, 245, 0.55);
font-size: 0.875rem;
}
.main__empty {
color: rgba(235, 235, 245, 0.45);
padding: 48px 0;
text-align: center;
font-size: 0.9375rem;
}
@media (max-width: 900px) {
.layout {
grid-template-columns: 1fr;
}
.sidebar {
border-right: none;
border-bottom: 0.5px solid rgba(255, 255, 255, 0.06);
}
.main {
padding: 24px 20px;
}
}
</style>