Tasks Module Stage 3: Checklists

- Add migration for task_checklists table
- Create TaskChecklistModel with CRUD methods
- Add TaskService methods for checklist operations
- Add API routes for checklist endpoints
- Add controller methods for checklist CRUD
- Add UI section in task view with AJAX handlers
This commit is contained in:
2026-02-08 18:40:41 +00:00
parent bf609257c3
commit 4a67f00aa7
6 changed files with 450 additions and 0 deletions
+123
View File
@@ -140,6 +140,51 @@
</div>
</div>
{# Чек-лист #}
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-light d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="fa-solid fa-check-square me-2"></i>Чек-лист
{% if task.checklists_count %}
<span class="badge bg-secondary ms-2">{{ task.checklists_completed }}/{{ task.checklists_count }}</span>
{% endif %}
</h5>
</div>
<div class="card-body">
{% if task.checklists %}
<ul class="list-group list-group-flush mb-3" id="checklists-list">
{% for checklist in task.checklists %}
<li class="list-group-item d-flex align-items-center gap-2" data-checklist-id="{{ checklist.id }}">
<input type="checkbox" class="form-check-input checklist-checkbox"
{% if checklist.is_completed %}checked{% endif %}
onchange="toggleChecklistItem({{ task.id }}, {{ checklist.id }})">
<span class="{% if checklist.is_completed %}text-muted text-decoration-line-through{% endif %} flex-grow-1">
{{ checklist.title }}
</span>
<button type="button" class="btn btn-outline-danger btn-sm"
onclick="deleteChecklistItem({{ task.id }}, {{ checklist.id }})"
title="Удалить">
<i class="fa-solid fa-times"></i>
</button>
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-muted text-center mb-3">Чек-лист пуст</p>
{% endif %}
<form action="{{ base_url('/tasks/' ~ task.id ~ '/checklists') }}" method="post"
class="d-flex gap-2 checklist-form" onsubmit="addChecklistItem(event, {{ task.id }})">
{{ csrf_field()|raw }}
<input type="text" name="title" class="form-control"
placeholder="Добавить пункт чек-листа..." required>
<button type="submit" class="btn btn-primary">
<i class="fa-solid fa-plus"></i>
</button>
</form>
</div>
</div>
{# Комментарии #}
<div class="card border-0 shadow-sm">
<div class="card-header bg-light">
@@ -314,5 +359,83 @@ function deleteSubtask(taskId, subtaskId) {
console.error('Error:', error);
});
}
// ========== Чек-листы ==========
function addChecklistItem(event, taskId) {
event.preventDefault();
const form = event.target;
const input = form.querySelector('input[name="title"]');
const title = input.value.trim();
if (!title) return;
fetch(`/tasks/${taskId}/checklists`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Requested-With': 'XMLHttpRequest'
},
body: 'title=' + encodeURIComponent(title)
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert(data.error || 'Ошибка при добавлении пункта чек-листа');
}
})
.catch(error => {
console.error('Error:', error);
alert('Ошибка при добавлении пункта чек-листа');
});
}
function toggleChecklistItem(taskId, checklistId) {
fetch(`/tasks/${taskId}/checklists/${checklistId}/toggle`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Requested-With': 'XMLHttpRequest'
},
body: ''
})
.then(response => response.json())
.then(data => {
if (!data.success) {
alert(data.error || 'Ошибка');
} else {
location.reload();
}
})
.catch(error => {
console.error('Error:', error);
});
}
function deleteChecklistItem(taskId, checklistId) {
if (!confirm('Удалить пункт чек-листа?')) return;
fetch(`/tasks/${taskId}/checklists/${checklistId}/delete`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Requested-With': 'XMLHttpRequest'
},
body: ''
})
.then(response => response.json())
.then(data => {
if (!data.success) {
alert(data.error || 'Ошибка');
} else {
location.reload();
}
})
.catch(error => {
console.error('Error:', error);
});
}
</script>
{% endblock %}