152 lines
3.2 KiB
Vue
152 lines
3.2 KiB
Vue
<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>
|