<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { api, type Project, type Task } from '../api'
import QuickPrompt from '../components/QuickPrompt.vue'
import TaskTree from '../components/TaskTree.vue'
defineOptions({ name: 'TreeView' })
// Дерево задач (основной вид, ТЗ 3.6): подзадачи — ветви произвольной вложенности.
// Сырые задачи живут в стеке; дерево показывает утверждённые. Наглядность
// (шевроны, линии, прогресс поддерева) — в компоненте TaskTree.
const { t } = useI18n()
const tasks = ref<Task[]>([])
const projects = ref<Project[]>([])
const error = ref('')
const loading = ref(false)
async function load() {
loading.value = true
error.value = ''
try {
tasks.value = await api.listTasks({ detail_state: 'approved' })
} catch (e) {
error.value = String(e)
} finally {
loading.value = false
}
}
onMounted(async () => {
await load()
projects.value = await api.listProjects()
})
async function setStatus(task: Task, status: string) {
error.value = ''
try {
await api.updateTask(task.id, { status })
await load()
} catch (e) {
error.value = String(e)
}
}
// Быстрое добавление подзадачи — модальное окно вместо window.prompt
const subtaskFor = ref<Task | null>(null)
const subtaskOpen = ref(false)
function openSubtaskPrompt(task: Task) {
subtaskFor.value = task
subtaskOpen.value = true
}
async function createSubtask(title: string) {
if (!subtaskFor.value) return
error.value = ''
try {
await api.createTask(title, '', subtaskFor.value.id)
// подзадача попадёт в стек; в дереве появится после утверждения
await load()
} catch (e) {
error.value = String(e)
}
}
// Удаление — только через подтверждение (действие необратимо)
const deleteTarget = ref<Task | null>(null)
const confirmOpen = ref(false)
function askDelete(task: Task) {
deleteTarget.value = task
confirmOpen.value = true
}
async function remove() {
const task = deleteTarget.value
if (!task) return
confirmOpen.value = false
error.value = ''
try {
await api.deleteTask(task.id)
await load()
} catch (e) {
error.value = String(e)
}
}
</script>
<template>
<section>
<GnPageHeader kicker="gntodo" :title="t('tree.title')">
<template #meta>{{ t('tree.count', { n: tasks.length }) }}</template>
</GnPageHeader>
<GnAlert v-if="error" variant="error">{{ error }}</GnAlert>
<GnSkeleton v-if="loading" type="line" stack :count="6" class="tree-skeleton" />
<GnEmptyState
v-if="!loading && tasks.length === 0"
icon="ph-tree-structure"
:title="t('tree.emptyTitle')"
:text="t('tree.emptyText')"
>
<template #actions>
<GnButton variant="primary" icon="ph-tray" @click="$router.push('/stack')">
{{ t('nav.stack') }}
</GnButton>
</template>
</GnEmptyState>
<TaskTree
:tasks="tasks"
@status-change="setStatus"
@add-subtask="openSubtaskPrompt"
@delete="askDelete"
/>
<QuickPrompt
v-model:open="subtaskOpen"
:title="t('tree.promptSubtask')"
@confirm="createSubtask"
/>
<GnConfirmDialog
v-model:open="confirmOpen"
:title="t('common.confirmTitle')"
:message="t('common.deleteConfirmTask', { title: deleteTarget?.title ?? '' })"
:confirm-text="t('common.delete')"
:cancel-text="t('common.cancel')"
confirm-variant="danger"
@confirm="remove"
/>
</section>
</template>
<style scoped>
.tree-skeleton {
margin-bottom: 1rem;
}
</style>