Osatehtävät (subtaskit) TODO-tehtäviin
Uusi todo_subtasks-taulu + 3 API-endpointtia (add/toggle/delete). Tehtävän lukunäkymässä checkbox-lista osatehtäville, lisäys Enter-näppäimellä tai Lisää-napilla. Valmiit yliviivataan. Tehtävälistassa näkyy edistyminen (esim. ☑ 2/5). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
49
script.js
49
script.js
@@ -3966,6 +3966,48 @@ function populateTodoAssignedFilter() {
|
||||
sel.innerHTML = '<option value="">Kaikki vastuuhenkilöt</option>' + users.map(u => `<option value="${esc(u)}">${esc(u)}</option>`).join('');
|
||||
}
|
||||
|
||||
// ---- Osatehtävät (subtaskit) ----
|
||||
|
||||
function renderSubtasks(subtasks, todoId) {
|
||||
const list = document.getElementById('task-subtasks-list');
|
||||
const countEl = document.getElementById('task-subtask-count');
|
||||
if (!list) return;
|
||||
const done = subtasks.filter(s => s.completed).length;
|
||||
const total = subtasks.length;
|
||||
if (countEl) countEl.textContent = total > 0 ? `(${done}/${total})` : '';
|
||||
list.innerHTML = subtasks.length ? subtasks.map(s => `<div class="subtask-item${s.completed ? ' completed' : ''}">
|
||||
<label><input type="checkbox" ${s.completed ? 'checked' : ''} onchange="toggleSubtask('${s.id}','${todoId}')"> <span>${esc(s.title)}</span></label>
|
||||
<button class="subtask-delete" onclick="deleteSubtask('${s.id}','${todoId}')" title="Poista">×</button>
|
||||
</div>`).join('') : '<div style="color:#aaa;font-size:0.85rem;">Ei osatehtäviä</div>';
|
||||
}
|
||||
|
||||
async function addSubtask(todoId) {
|
||||
const input = document.getElementById('subtask-input');
|
||||
const title = (input?.value || '').trim();
|
||||
if (!title) return;
|
||||
try {
|
||||
await apiCall('todo_subtask_add', 'POST', { todo_id: todoId, title });
|
||||
input.value = '';
|
||||
await openTaskRead(todoId);
|
||||
} catch (e) { alert(e.message); }
|
||||
}
|
||||
|
||||
async function toggleSubtask(subtaskId, todoId) {
|
||||
try {
|
||||
await apiCall('todo_subtask_toggle', 'POST', { id: subtaskId });
|
||||
await openTaskRead(todoId);
|
||||
await loadTodos();
|
||||
} catch (e) { alert(e.message); }
|
||||
}
|
||||
|
||||
async function deleteSubtask(subtaskId, todoId) {
|
||||
try {
|
||||
await apiCall('todo_subtask_delete', 'POST', { id: subtaskId });
|
||||
await openTaskRead(todoId);
|
||||
await loadTodos();
|
||||
} catch (e) { alert(e.message); }
|
||||
}
|
||||
|
||||
// ---- Tehtävät ----
|
||||
|
||||
function renderTasksList() {
|
||||
@@ -4013,7 +4055,7 @@ function renderTasksList() {
|
||||
<td><span class="todo-status status-${t.status}">${todoStatusLabels[t.status]||t.status}</span></td>
|
||||
<td><span class="priority-badge priority-${t.priority}">${todoPriorityLabels[t.priority]||t.priority}</span></td>
|
||||
<td>${t.category ? `<span class="todo-category cat-${t.category}">${todoCategoryLabels[t.category]||t.category}</span>` : '<span style="color:#ccc;">—</span>'}</td>
|
||||
<td><strong>${esc(t.title)}</strong></td>
|
||||
<td><strong>${esc(t.title)}</strong>${t.subtask_count > 0 ? ` <span class="subtask-progress">☑ ${t.subtask_done}/${t.subtask_count}</span>` : ''}</td>
|
||||
<td>${t.assigned_to ? esc(t.assigned_to) : '<span style="color:#ccc;">—</span>'}</td>
|
||||
<td style="text-align:center;">${t.total_hours > 0 ? t.total_hours + 'h' : '<span style="color:#ccc;">—</span>'}</td>
|
||||
<td style="text-align:center;">${t.comment_count > 0 ? t.comment_count : ''}</td>
|
||||
@@ -4063,6 +4105,11 @@ async function openTaskRead(id) {
|
||||
<td>${e.work_date}</td><td>${esc(e.user)}</td><td>${e.hours}h</td><td>${esc(e.description||'')}</td>
|
||||
<td>${(e.user === currentUser?.username || isAdmin) ? `<button onclick="deleteTimeEntry('${e.id}','${t.id}')" style="background:none;border:none;cursor:pointer;color:#ccc;font-size:1rem;" title="Poista">🗑</button>` : ''}</td>
|
||||
</tr>`).join('') : '<tr><td colspan="5" style="color:#aaa;text-align:center;">Ei kirjauksia</td></tr>';
|
||||
// Osatehtävät
|
||||
renderSubtasks(t.subtasks || [], t.id);
|
||||
document.getElementById('btn-add-subtask')?.replaceWith(document.getElementById('btn-add-subtask')?.cloneNode(true));
|
||||
document.getElementById('btn-add-subtask')?.addEventListener('click', () => addSubtask(t.id));
|
||||
document.getElementById('subtask-input')?.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); addSubtask(t.id); } });
|
||||
// Kommentit
|
||||
renderTodoComments(t.comments || [], 'task');
|
||||
document.getElementById('task-comment-count').textContent = `(${(t.comments||[]).length})`;
|
||||
|
||||
Reference in New Issue
Block a user