127 lines
5.4 KiB
HTML
127 lines
5.4 KiB
HTML
{% extends 'base.html' %}
|
|
{% load static %}
|
|
{% load core_tags %}
|
|
|
|
{% block title %}AI Task Manager - Kanban Board{% endblock %}
|
|
|
|
{% block container_class %}container-fluid{% endblock %}
|
|
|
|
{% block content %}
|
|
|
|
|
|
<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">
|
|
<form action="{% url 'core:delete_task' item.id %}" method="post" class="delete-task-form">
|
|
{% csrf_token %}
|
|
<button type="submit" class="btn-close" aria-label="Close"></button>
|
|
</form>
|
|
<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 %}
|
|
{% if item.deadline %}
|
|
<p class="card-text small text-muted mt-2">Deadline: {{ item.deadline|date:"Y-m-d" }}</p>
|
|
{% 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,
|
|
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.');
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
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 %}
|