bp/app/Modules/Tasks/Views/tasks/kanban.twig

200 lines
9.0 KiB
Twig
Raw Permalink 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.

{% extends 'layouts/base.twig' %}
{% block title %}{{ title }} — Бизнес.Точка{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0">{{ title }}</h1>
<div class="d-flex gap-3 align-items-center">
{# Выбор доски #}
<select class="form-select" style="width: 200px" onchange="window.location.href = '{{ base_url('/tasks/kanban?board=') }}' + this.value">
{% for b in boards %}
<option value="{{ b.id }}" {{ board.id == b.id ? 'selected' : '' }}>{{ b.name }}</option>
{% endfor %}
</select>
<div class="btn-group">
<a href="{{ base_url('/tasks') }}" class="btn btn-outline-secondary">
<i class="fa-solid fa-list me-2"></i>Список
</a>
<a href="{{ base_url('/tasks/calendar') }}" class="btn btn-outline-primary">
<i class="fa-solid fa-calendar me-2"></i>Календарь
</a>
</div>
<a href="{{ base_url('/tasks/new?board=' ~ board.id) }}" class="btn btn-primary">
<i class="fa-solid fa-plus me-2"></i>Добавить задачу
</a>
</div>
</div>
{# Статистика #}
<div class="row g-3 mb-4">
<div class="col-md-3">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body text-center">
<h5 class="card-title text-muted mb-0">Всего</h5>
<h2 class="mb-0">{{ stats.total }}</h2>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body text-center">
<h5 class="card-title text-muted mb-0">Выполнено</h5>
<h2 class="mb-0 text-success">{{ stats.completed }}</h2>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body text-center">
<h5 class="card-title text-muted mb-0">В ожидании</h5>
<h2 class="mb-0 text-primary">{{ stats.pending }}</h2>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body text-center">
<h5 class="card-title text-muted mb-0">Просрочено</h5>
<h2 class="mb-0 text-danger">{{ stats.overdue }}</h2>
</div>
</div>
</div>
</div>
{# Канбан доска #}
<div class="kanban-container" style="overflow-x: auto; padding-bottom: 1rem;">
<div class="d-flex gap-3" style="min-width: max-content;">
{% for column in kanbanColumns %}
<div class="kanban-column" style="width: 320px; min-width: 320px;">
<div class="card border-0 shadow-sm">
<div class="card-header d-flex justify-content-between align-items-center" style="background-color: {{ column.color }}; color: white;">
<h6 class="mb-0 fw-bold">{{ column.name }}</h6>
<span class="badge bg-light text-dark">{{ column.items|length }}</span>
</div>
<div class="card-body p-2 kanban-items" data-column-id="{{ column.id }}"
style="min-height: 400px; max-height: 600px; overflow-y: auto;">
{% for item in column.items %}
<div class="card mb-2 kanban-item" data-task-id="{{ item.id }}" style="cursor: grab;">
<div class="card-body p-3">
<div class="d-flex justify-content-between align-items-start mb-2">
<h6 class="card-title mb-0">{{ item.title }}</h6>
{% if item.priority == 'urgent' %}
<span class="badge bg-danger" style="font-size: 0.6rem;">Срочно</span>
{% elseif item.priority == 'high' %}
<span class="badge bg-warning text-dark" style="font-size: 0.6rem;">Высокий</span>
{% endif %}
</div>
{% if item.description %}
<p class="card-text text-muted small mb-2">
{{ item.description|length > 50 ? item.description|slice(0, 50) ~ '...' : item.description }}
</p>
{% endif %}
<div class="d-flex justify-content-between align-items-center">
{% if item.due_date %}
<small class="text-muted">
<i class="fa-regular fa-calendar me-1"></i>
{{ item.due_date|date('d.m') }}
{% if item.due_date < date('Y-m-d') %}
<span class="text-danger">!</span>
{% endif %}
</small>
{% endif %}
<a href="{{ base_url('/tasks/' ~ item.id) }}" class="btn btn-sm btn-outline-primary">
<i class="fa-solid fa-arrow-right"></i>
</a>
</div>
</div>
</div>
{% endfor %}
{# Кнопка добавления #}
<a href="{{ base_url('/tasks/new?board=' ~ board.id ~ '&column=' ~ column.id) }}" class="btn btn-outline-secondary btn-sm w-100 mt-2">
<i class="fa-solid fa-plus me-1"></i>Добавить задачу
</a>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<form id="move-task-form" action="{{ base_url('/tasks/move-column') }}" method="post" style="display: none;">
{{ csrf_field|raw }}
<input type="hidden" name="task_id" id="move-task-id">
<input type="hidden" name="column_id" id="move-column-id">
</form>
{% endblock %}
{% block scripts %}
<script>
// Drag and drop для Канбана
document.addEventListener('DOMContentLoaded', function() {
const columns = document.querySelectorAll('.kanban-items');
let draggedItem = null;
columns.forEach(column => {
column.addEventListener('dragover', function(e) {
e.preventDefault();
this.style.backgroundColor = '#f8f9fa';
});
column.addEventListener('dragleave', function() {
this.style.backgroundColor = '';
});
column.addEventListener('drop', function(e) {
e.preventDefault();
this.style.backgroundColor = '';
if (draggedItem && draggedItem.dataset.taskId) {
const taskId = draggedItem.dataset.taskId;
const newColumnId = this.dataset.columnId;
// Отправляем запрос на перемещение
document.getElementById('move-task-id').value = taskId;
document.getElementById('move-column-id').value = newColumnId;
fetch('{{ base_url('/tasks/move-column') }}', {
method: 'POST',
body: new FormData(document.getElementById('move-task-form'))
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Перезагружаем страницу для обновления
location.reload();
} else {
alert('Ошибка при перемещении задачи');
}
})
.catch(error => {
console.error('Error:', error);
alert('Ошибка при перемещении задачи');
});
}
draggedItem = null;
});
});
document.querySelectorAll('.kanban-item').forEach(item => {
item.addEventListener('dragstart', function() {
draggedItem = this;
this.style.opacity = '0.5';
});
item.addEventListener('dragend', function() {
this.style.opacity = '1';
draggedItem = null;
});
});
});
</script>
{% endblock %}