35905-vm/core/templates/core/kanban.html
2025-11-19 22:33:08 +00:00

127 lines
5.0 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% load core_tags %}
{% block title %}AI Task Manager - Kanban Board{% endblock %}
{% block content %}
<div class="hero-section text-center py-5">
<h1 class="display-4">Kanban Board</h1>
<p class="lead">Visualize your tasks and track progress.</p>
</div>
<div class="kanban-board-container">
<div class="loader-overlay" style="display: none;">
<div class="loader"></div>
</div>
{% csrf_token %}
<div class="kanban-board">
{% for status_value, status_display in status_choices %}
<div class="kanban-column" data-status="{{ status_value }}">
<h2 class="h5 p-3 bg-light border-bottom">{{ status_display }}</h2>
<div class="kanban-cards p-3">
{% with tasks=tasks_by_status|get_item:status_value %}
{% if tasks %}
{% for item in tasks %}
<div class="card kanban-card mb-3 shadow-sm" data-task-id="{{ item.id }}">
<div class="card-body">
<h5 class="card-title h6">{{ item.title }}</h5>
<p class="card-text small">{{ item.description|default:""|truncatewords:15 }}</p>
{% if item.tags %}
<div class="tags">
{% for tag in item.tags.split|slice:":3" %}
<span class="badge bg-secondary">{{ tag }}</span>
{% endfor %}
</div>
{% endif %}
</div>
</div>
{% endfor %}
{% else %}
<div class="text-center text-muted p-3">No tasks in this stage.</div>
{% endif %}
{% endwith %}
</div>
</div>
{% endfor %}
</div>
</div>
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function () {
const columns = document.querySelectorAll('.kanban-cards');
columns.forEach(column => {
new Sortable(column, {
group: 'kanban',
animation: 150,
onStart: function (evt) {
document.querySelector('.loader-overlay').style.display = 'flex';
},
onEnd: function (evt) {
const itemEl = evt.item;
const toContainer = evt.to;
const fromContainer = evt.from;
const taskId = itemEl.dataset.taskId;
const newStatus = toContainer.closest('.kanban-column').dataset.status;
const oldIndex = evt.oldDraggableIndex;
// Get CSRF token
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]') ? document.querySelector('[name=csrfmiddlewaretoken]').value : getCookie('csrftoken');
fetch('/update_task_status/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken
},
body: JSON.stringify({
task_id: taskId,
new_status: newStatus
})
})
.then(response => {
if (!response.ok) {
throw new Error('Server responded with an error!');
}
return response.json();
})
.then(data => {
if (!data.success) {
// Revert the move in the UI
fromContainer.insertBefore(itemEl, fromContainer.children[oldIndex]);
alert('Failed to update task status. Please try again.');
}
})
.catch(error => {
console.error('Error updating task status:', error);
// Revert the move in the UI
fromContainer.insertBefore(itemEl, fromContainer.children[oldIndex]);
alert('An error occurred while updating the task. Please try again.');
})
.finally(() => {
document.querySelector('.loader-overlay').style.display = 'none';
});
}
});
});
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
});
</script>
{% endblock %}
{% endblock %}