200 lines
9.0 KiB
Twig
200 lines
9.0 KiB
Twig
{% 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 %}
|