Newer
Older
gnexus-tasks / frontend / src / views / StackView.vue
<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>