Files
Avtex/app/pages/tasks.vue
T

220 lines
7.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="min-h-screen bg-slate-900 text-slate-200">
<div class="max-w-7xl mx-auto px-4 py-8">
<div class="flex items-center justify-between mb-8">
<h1 class="text-3xl font-bold text-white flex items-center gap-3">
<span class="text-blue-500">📋</span> Мои Задачи
</h1>
</div>
<div v-if="loading" class="flex items-center justify-center h-64">
<div class="w-12 h-12 border-4 border-blue-500 border-t-transparent rounded-full animate-spin"></div>
</div>
<div v-else-if="tasks.length === 0" class="flex flex-col items-center justify-center h-64 bg-slate-800 rounded-xl border border-slate-700">
<span class="text-4xl mb-4">📭</span>
<h3 class="text-xl font-medium text-white mb-2">У вас пока нет задач</h3>
<p class="text-slate-400">Вам не назначено ни одной задачи в проектах.</p>
</div>
<div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<!-- Kanban Columns -->
<div v-for="col in columns" :key="col.id" class="bg-slate-800 rounded-xl border border-slate-700 flex flex-col max-h-[80vh]">
<div class="p-4 border-b border-slate-700 flex items-center justify-between sticky top-0 bg-slate-800 rounded-t-xl z-10">
<h3 class="font-bold text-white flex items-center gap-2">
<span :class="col.color">{{ col.icon }}</span> {{ col.title }}
</h3>
<span class="bg-slate-700 text-slate-300 text-xs px-2 py-1 rounded-full font-medium">
{{ getTasksForColumn(col.id).length }}
</span>
</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="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>
</div>
</div>
</div>
</div>
</template>
<script setup>
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' },
{ id: 'in_progress', title: 'В работе', icon: '⏳', color: 'text-blue-400' },
{ id: 'review', title: 'На проверке', icon: '👀', color: 'text-purple-400' },
{ id: 'blocked', title: 'Заблокировано', icon: '⛔', color: 'text-red-400' },
{ id: 'completed', title: 'Завершено', icon: '✅', color: 'text-emerald-400' }
]
// Fetch all tasks for current user
const fetchTasks = async () => {
try {
const res = await fetch('/api/tasks', {
headers: { Authorization: `Bearer ${auth.token.value}` }
})
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)
} finally {
loading.value = false
}
}
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'
return taskState === state
})
}
const getTaskTitle = (task) => {
if (task.label) return task.label
const text = task.text || task.data?.text
if (text) {
const lines = text.split('\n')
const firstLine = lines.find(l => l.trim().startsWith('# ')) || lines.find(l => l.trim())
return firstLine ? firstLine.replace('# ', '').trim().slice(0, 50) : 'Без названия'
}
return 'Задача'
}
const getPriorityClass = (task) => {
const priority = task.priority || task.data?.priority
if (priority === 'high') return 'bg-red-900/50 text-red-300'
if (priority === 'low') return 'bg-slate-700 text-slate-300'
return 'bg-blue-900/50 text-blue-300' // medium
}
const getPriorityLabel = (task) => {
const priority = task.priority || task.data?.priority
if (priority === 'high') return 'Высокий'
if (priority === 'low') return 'Низкий'
return 'Средний'
}
const formatShortDate = (dateStr) => {
try {
const d = new Date(dateStr)
return d.toLocaleDateString('ru-RU', { day: 'numeric', month: 'short' })
} catch {
return dateStr
}
}
const openTask = (task) => {
navigateTo(`/canvas/${task.canvasId}`)
}
onMounted(async () => {
if (!auth.user.value) {
await auth.init()
}
await fetchTasks()
})
</script>
<style scoped>
.custom-scrollbar {
scrollbar-width: thin;
scrollbar-color: #475569 transparent;
}
.custom-scrollbar::-webkit-scrollbar {
width: 4px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background-color: #475569;
border-radius: 4px;
}
</style>