1
0

Инициализация проекта

Загрузка проекта на GIT
This commit is contained in:
2026-01-11 15:01:35 +07:00
commit 301e179160
28 changed files with 7769 additions and 0 deletions

View File

@@ -0,0 +1,217 @@
<template>
<div class="board">
<div class="columns">
<Column
v-for="column in filteredColumns"
:key="column.id"
:column="column"
:departments="departments"
:labels="labels"
@drop-card="handleDropCard"
@open-task="(card) => emit('open-task', { card, columnId: column.id })"
@create-task="emit('create-task', column.id)"
/>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUpdated, watch } from 'vue'
import Column from './Column.vue'
const props = defineProps({
activeDepartment: Number,
departments: {
type: Array,
default: () => []
},
labels: {
type: Array,
default: () => []
},
columns: {
type: Array,
default: () => []
},
cards: {
type: Array,
default: () => []
}
})
const emit = defineEmits(['stats-updated', 'open-task', 'create-task'])
const refreshIcons = () => {
if (window.lucide) {
window.lucide.createIcons()
}
}
onMounted(refreshIcons)
onUpdated(refreshIcons)
// Локальная копия карточек для optimistic UI
const localCards = ref([])
// Синхронизируем с props при загрузке данных
watch(() => props.cards, (newCards) => {
if (newCards.length > 0 && localCards.value.length === 0) {
// Первая загрузка - копируем данные и добавляем order если нет
localCards.value = JSON.parse(JSON.stringify(newCards)).map((card, idx) => ({
...card,
order: card.order ?? idx
}))
}
}, { immediate: true })
// Собираем колонки с карточками (используем localCards, сортируем по order)
const columnsWithCards = computed(() => {
return props.columns.map(col => ({
id: col.id,
title: col.name,
color: col.color,
cards: localCards.value
.filter(card => card.column_id === col.id)
.sort((a, b) => a.order - b.order)
.map(card => ({
id: card.id_card,
title: card.title,
description: card.descript,
details: card.descript_full,
departmentId: card.id_department,
labelId: card.id_label,
assignee: card.avatar_img,
dueDate: card.date,
dateCreate: card.date_create,
files: card.files || (card.file_img || []).map(f => ({
name: f.name,
url: f.url,
size: f.size,
preview: f.url
}))
}))
}))
})
// Фильтруем колонки по активному отделу
const filteredColumns = computed(() => {
if (!props.activeDepartment) return columnsWithCards.value
return columnsWithCards.value.map(col => ({
...col,
cards: col.cards.filter(card => card.departmentId === props.activeDepartment)
}))
})
const filteredTotalTasks = computed(() => {
return filteredColumns.value.reduce((sum, col) => sum + col.cards.length, 0)
})
const inProgressTasks = computed(() => {
const col = filteredColumns.value.find(c => c.id === 3) // В работе
return col ? col.cards.length : 0
})
const completedTasks = computed(() => {
const col = filteredColumns.value.find(c => c.id === 5) // Готово
return col ? col.cards.length : 0
})
// Отправляем статистику в родителя
watch([filteredTotalTasks, inProgressTasks, completedTasks], () => {
emit('stats-updated', {
total: filteredTotalTasks.value,
inProgress: inProgressTasks.value,
done: completedTasks.value
})
}, { immediate: true })
const handleDropCard = ({ cardId, fromColumnId, toColumnId, toIndex }) => {
const card = localCards.value.find(c => c.id_card === cardId)
if (!card) return
// Меняем колонку
card.column_id = toColumnId
// Получаем карточки целевой колонки (без перемещаемой)
const columnCards = localCards.value
.filter(c => c.column_id === toColumnId && c.id_card !== cardId)
.sort((a, b) => a.order - b.order)
// Вставляем карточку в нужную позицию и пересчитываем order
columnCards.splice(toIndex, 0, card)
columnCards.forEach((c, idx) => {
c.order = idx
})
// TODO: отправить изменение на сервер
}
// Генератор id для новых карточек
let nextCardId = 100
// Методы для модалки
const saveTask = (taskData, columnId) => {
if (taskData.id) {
// Редактирование существующей карточки
const card = localCards.value.find(c => c.id_card === taskData.id)
if (card) {
card.title = taskData.title
card.descript = taskData.description
card.descript_full = taskData.details
card.id_department = taskData.departmentId
card.id_label = taskData.labelId
card.date = taskData.dueDate
card.avatar_img = taskData.assignee
card.files = taskData.files || []
}
} else {
// Создание новой карточки (в конец колонки)
const columnCards = localCards.value.filter(c => c.column_id === columnId)
const maxOrder = columnCards.length > 0
? Math.max(...columnCards.map(c => c.order)) + 1
: 0
localCards.value.push({
id_card: nextCardId++,
id_department: taskData.departmentId,
id_label: taskData.labelId,
title: taskData.title,
descript: taskData.description,
descript_full: taskData.details,
avatar_img: taskData.assignee,
column_id: columnId,
date: taskData.dueDate,
date_create: new Date().toISOString().split('T')[0],
order: maxOrder,
files: taskData.files || []
})
}
// TODO: отправить на сервер
}
const deleteTask = (cardId, columnId) => {
const index = localCards.value.findIndex(c => c.id_card === cardId)
if (index !== -1) {
localCards.value.splice(index, 1)
}
// TODO: отправить на сервер
}
defineExpose({ saveTask, deleteTask })
</script>
<style scoped>
.board {
min-width: max-content;
}
.columns {
display: flex;
gap: 20px;
align-items: flex-start;
}
</style>