feat: AI context, Webhooks, Kanban drag-drop, and UI fixes

This commit is contained in:
2026-04-29 17:43:18 +05:00
parent 89b3340253
commit bc2f99408d
29 changed files with 991 additions and 52 deletions
+78 -29
View File
@@ -29,35 +29,45 @@
</span>
</div>
<div class="p-4 flex-1 overflow-y-auto space-y-4 custom-scrollbar">
<div
v-for="task in getTasksForColumn(col.id)"
:key="task.id"
class="bg-slate-900 border border-slate-600 rounded-lg p-4 shadow-sm hover:border-blue-500 transition-colors cursor-pointer group"
@click="openTask(task)"
>
<div class="flex items-start justify-between mb-2">
<div class="text-xs font-medium px-2 py-0.5 rounded" :class="getPriorityClass(task)">
{{ getPriorityLabel(task) }}
</div>
<div class="text-[10px] text-slate-500 font-mono">{{ task.id.split('-')[1] || task.id }}</div>
</div>
<h4 class="font-medium text-white mb-2 line-clamp-2">{{ getTaskTitle(task) }}</h4>
<div class="flex items-center justify-between mt-4">
<div class="text-xs text-slate-400 flex items-center gap-1">
<span>🗺️</span>
<span class="truncate max-w-[120px]">{{ task.canvasName }}</span>
</div>
<div v-if="task.alarm || task.data?.alarm" class="text-xs text-orange-400 flex items-center gap-1" :title="new Date(task.alarm || task.data?.alarm).toLocaleString('ru-RU')">
<span>⏰</span>
<span>{{ formatShortDate(task.alarm || task.data?.alarm) }}</span>
</div>
</div>
</div>
<div class="p-4 flex-1 overflow-y-auto space-y-4 custom-scrollbar min-h-[200px]">
<ClientOnly>
<draggable
v-model="columnsData[col.id]"
group="tasks"
item-key="id"
class="space-y-4 min-h-[200px]"
@change="(evt) => onTaskMove(evt, col.id)"
>
<template #item="{ element: task }">
<div
class="bg-slate-900 border border-slate-600 rounded-lg p-4 shadow-sm hover:border-blue-500 transition-colors cursor-pointer group"
@click="openTask(task)"
>
<div class="flex items-start justify-between mb-2">
<div class="text-xs font-medium px-2 py-0.5 rounded" :class="getPriorityClass(task)">
{{ getPriorityLabel(task) }}
</div>
<div class="text-[10px] text-slate-500 font-mono">{{ task.id.split('-')[1] || task.id }}</div>
</div>
<h4 class="font-medium text-white mb-2 line-clamp-2">{{ getTaskTitle(task) }}</h4>
<div class="flex items-center justify-between mt-4">
<div class="text-xs text-slate-400 flex items-center gap-1">
<span>🗺️</span>
<span class="truncate max-w-[120px]">{{ task.canvasName }}</span>
</div>
<div v-if="task.alarm || task.data?.alarm" class="text-xs text-orange-400 flex items-center gap-1" :title="new Date(task.alarm || task.data?.alarm).toLocaleString('ru-RU')">
<span>⏰</span>
<span>{{ formatShortDate(task.alarm || task.data?.alarm) }}</span>
</div>
</div>
</div>
</template>
</draggable>
</ClientOnly>
<div v-if="getTasksForColumn(col.id).length === 0" class="text-center py-6 border-2 border-dashed border-slate-700 rounded-lg">
<div v-if="columnsData[col.id].length === 0" class="text-center py-6 border-2 border-dashed border-slate-700 rounded-lg pointer-events-none">
<span class="text-slate-500 text-sm">Нет задач</span>
</div>
</div>
@@ -68,11 +78,19 @@
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
import { ref, onMounted, computed, reactive } from 'vue'
import draggable from 'vuedraggable'
const auth = useAuth()
const loading = ref(true)
const tasks = ref([])
const columnsData = reactive({
todo: [],
in_progress: [],
review: [],
blocked: [],
completed: []
})
const columns = [
{ id: 'todo', title: 'К выполнению', icon: '📋', color: 'text-slate-400' },
@@ -91,6 +109,14 @@ const fetchTasks = async () => {
if (res.ok) {
const data = await res.json()
tasks.value = data.tasks || []
// Split tasks into columns
columns.forEach(col => {
columnsData[col.id] = tasks.value.filter(t => {
const taskState = t.state || t.data?.state || 'todo'
return taskState === col.id
})
})
}
} catch (e) {
console.error('Failed to load tasks', e)
@@ -99,6 +125,29 @@ const fetchTasks = async () => {
}
}
const onTaskMove = async (evt, newStatus) => {
if (evt.added) {
const task = evt.added.element
console.log(`Moving task ${task.id} to ${newStatus}`)
try {
await $fetch('/api/tasks/update-status', {
method: 'POST',
body: {
taskId: task.id,
canvasId: task.canvasId,
newState: newStatus
},
headers: { Authorization: `Bearer ${auth.token.value}` }
})
} catch (e) {
console.error('Failed to update task status', e)
// Rollback on error? (In a production app, we should)
alert('Не удалось обновить статус задачи на сервере')
}
}
}
const getTasksForColumn = (state) => {
return tasks.value.filter(t => {
const taskState = t.state || t.data?.state || 'todo'