Tasks Module Stage 2: Subtasks

- Create task_subtasks table migration
- Create TaskSubtaskModel with CRUD operations
- Add subtask API methods: addSubtask, toggleSubtask, deleteSubtask
- Update TaskService to include subtasks in getTask()
- Add Routes for subtasks API
- Update show.twig with subtasks UI and JavaScript
This commit is contained in:
2026-02-08 14:55:45 +00:00
parent d7fec7169f
commit cee6c636ad
6 changed files with 417 additions and 0 deletions
+95
View File
@@ -95,6 +95,50 @@
</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-list-check me-2"></i>Подзадачи
{% if task.subtasks_count %}
<span class="badge bg-secondary ms-2">{{ task.subtasks_completed }}/{{ task.subtasks_count }}</span>
{% endif %}
</h5>
</div>
<div class="card-body">
{% if task.subtasks %}
<ul class="list-group list-group-flush mb-3" id="subtasks-list">
{% for subtask in task.subtasks %}
<li class="list-group-item d-flex align-items-center gap-2" data-subtask-id="{{ subtask.id }}">
<input type="checkbox" class="form-check-input subtask-checkbox"
{% if subtask.is_completed %}checked{% endif %}
onchange="toggleSubtask({{ task.id }}, {{ subtask.id }})">
<span class="{% if subtask.is_completed %}text-muted text-decoration-line-through{% endif %} flex-grow-1">
{{ subtask.title }}
</span>
<button type="button" class="btn btn-outline-danger btn-sm"
onclick="deleteSubtask({{ task.id }}, {{ subtask.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 ~ '/subtasks') }}" method="post" class="d-flex gap-2">
{{ 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">
@@ -178,3 +222,54 @@
</div>
</div>
{% endblock %}
{% block scripts %}
{{ parent() }}
<script>
function toggleSubtask(taskId, subtaskId) {
fetch(`/tasks/${taskId}/subtasks/${subtaskId}/toggle`, {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
}
})
.then(response => response.json())
.then(data => {
if (!data.success) {
alert(data.error || 'Ошибка');
location.reload();
} else {
// Перезагружаем страницу для обновления UI
location.reload();
}
})
.catch(error => {
console.error('Error:', error);
location.reload();
});
}
function deleteSubtask(taskId, subtaskId) {
if (!confirm('Удалить подзадачу?')) return;
fetch(`/tasks/${taskId}/subtasks/${subtaskId}/delete`, {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
}
})
.then(response => response.json())
.then(data => {
if (!data.success) {
alert(data.error || 'Ошибка');
} else {
// Удаляем из DOM
document.querySelector(`li[data-subtask-id="${subtaskId}"]`).remove();
}
})
.catch(error => {
console.error('Error:', error);
});
}
</script>
{% endblock %}