<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { api, type Project, type Task } from '../api'
// Стек входящих: сырые задачи, ожидающие детализации и утверждения.
const stack = ref<Task[]>([])
const projects = ref<Project[]>([])
const tags = ref<{ id: number; name: string }[]>([])
const quickTitle = ref('')
const error = ref('')
const loading = ref(false)
const editing = ref<Task | null>(null)
const editForm = ref({
title: '',
description: '',
project_id: null as number | null,
tag_ids: [] as number[],
priority: null as number | null,
})
async function loadStack() {
loading.value = true
try {
stack.value = await api.listTasks({ detail_state: 'raw' })
} catch (e) {
error.value = String(e)
} finally {
loading.value = false
}
}
async function quickAdd() {
const title = quickTitle.value.trim()
if (!title) return
error.value = ''
try {
await api.createTask(title)
quickTitle.value = ''
await loadStack()
} catch (e) {
error.value = String(e)
}
}
function startEdit(task: Task) {
editing.value = task
editForm.value = {
title: task.title,
description: task.description,
project_id: task.project?.id ?? null,
tag_ids: task.tags.map((t) => t.id),
priority: task.priority,
}
}
async function saveEdit() {
if (!editing.value) return
error.value = ''
try {
await api.updateTask(editing.value.id, editForm.value)
await api.approveTask(editing.value.id)
editing.value = null
await loadStack()
} catch (e) {
error.value = String(e)
}
}
async function approve(task: Task) {
error.value = ''
try {
await api.approveTask(task.id)
await loadStack()
} catch (e) {
error.value = String(e)
}
}
async function remove(task: Task) {
error.value = ''
try {
await api.deleteTask(task.id)
await loadStack()
} catch (e) {
error.value = String(e)
}
}
async function createProjectInline() {
const name = window.prompt('Название нового проекта:')
if (!name) return
try {
const p = await api.createProject(name.trim())
projects.value.push(p)
editForm.value.project_id = p.id
} catch (e) {
error.value = String(e)
}
}
async function createTagInline() {
const name = window.prompt('Название нового тега:')
if (!name) return
try {
const t = await api.createTag(name.trim())
tags.value.push(t)
editForm.value.tag_ids.push(t.id)
} catch (e) {
error.value = String(e)
}
}
onMounted(async () => {
await loadStack()
projects.value = await api.listProjects()
tags.value = await api.listTags()
})
</script>
<template>
<section class="stack">
<h1>Стек входящих <span class="count">{{ stack.length }}</span></h1>
<!-- Быстрый ввод: одна строка, без метаданных -->
<form class="quick-add" @submit.prevent="quickAdd">
<input
v-model="quickTitle"
type="text"
placeholder="Закинуть задачу — только текст, остальное потом…"
autofocus
/>
<button type="submit">Добавить</button>
</form>
<p v-if="error" class="error">{{ error }}</p>
<p v-if="!loading && stack.length === 0" class="empty">Стек пуст — закиньте первую задачу.</p>
<ul class="task-list">
<li v-for="task in stack" :key="task.id" class="task-item">
<template v-if="editing?.id === task.id">
<!-- Редактирование: предложенные метаданные правятся вручную, затем утверждение -->
<form class="edit-form" @submit.prevent="saveEdit">
<input v-model="editForm.title" type="text" placeholder="Заголовок" required />
<textarea v-model="editForm.description" rows="4" placeholder="Описание (Markdown)" />
<div class="form-row">
<label>Проект</label>
<select v-model="editForm.project_id">
<option :value="null">— нет —</option>
<option v-for="p in projects" :key="p.id" :value="p.id">{{ p.name }}</option>
</select>
<button type="button" @click="createProjectInline">+ проект</button>
</div>
<div class="form-row">
<label>Теги</label>
<select multiple v-model="editForm.tag_ids" size="4">
<option v-for="t in tags" :key="t.id" :value="t.id">{{ t.name }}</option>
</select>
<button type="button" @click="createTagInline">+ тег</button>
</div>
<div class="form-row">
<label>Приоритет</label>
<input v-model.number="editForm.priority" type="number" min="0" max="10" />
</div>
<div class="form-actions">
<button type="submit" class="accent">Утвердить</button>
<button type="button" @click="editing = null">Отмена</button>
</div>
</form>
</template>
<template v-else>
<div class="task-row">
<span class="task-title">{{ task.title }}</span>
<span class="task-meta">{{ new Date(task.created_at).toLocaleString('ru') }}</span>
<div class="task-actions">
<button type="button" class="accent" @click="approve(task)">Да, всё верно</button>
<button type="button" @click="startEdit(task)">Детализировать</button>
<button type="button" @click="remove(task)">Удалить</button>
</div>
</div>
</template>
</li>
</ul>
</section>
</template>
<style scoped>
.stack { max-width: 720px; }
.count { color: var(--muted, #888); font-size: 0.9em; }
.quick-add { display: flex; gap: 0.5rem; margin-bottom: 1.5rem; }
.quick-add input { flex: 1; padding: 0.6rem; }
.task-list { list-style: none; padding: 0; }
.task-item { border-bottom: 1px solid var(--border, #333); padding: 0.6rem 0; }
.task-row { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
.task-title { flex: 1; min-width: 200px; }
.task-meta { color: var(--muted, #888); font-size: 0.85em; }
.task-actions, .form-actions { display: flex; gap: 0.5rem; }
.edit-form { display: flex; flex-direction: column; gap: 0.75rem; padding: 0.5rem 0; }
.edit-form input, .edit-form textarea, .edit-form select { width: 100%; box-sizing: border-box; }
.form-row { display: flex; gap: 0.5rem; align-items: center; }
.form-row label { min-width: 70px; }
.error { color: #e06c75; }
.empty { color: var(--muted, #888); }
.accent { font-weight: 600; }
button { cursor: pointer; }
</style>