diff --git a/api.php b/api.php index ddcd610..2f09f3d 100644 --- a/api.php +++ b/api.php @@ -2423,6 +2423,68 @@ switch ($action) { } break; + case 'todo_subtask_add': + requireAuth(); + $companyId = requireCompany(); + if ($method !== 'POST') { echo json_encode(['error' => 'POST required']); break; } + try { + $input = json_decode(file_get_contents('php://input'), true); + $todoId = $input['todo_id'] ?? ''; + $title = trim($input['title'] ?? ''); + if (!$todoId || !$title) { echo json_encode(['error' => 'todo_id ja title vaaditaan']); break; } + $rows = _dbFetchAll("SELECT company_id FROM todos WHERE id = ?", [$todoId]); + if (empty($rows) || $rows[0]['company_id'] !== $companyId) { + http_response_code(404); + echo json_encode(['error' => 'Tehtävää ei löytynyt']); + break; + } + $id = generateId(); + dbAddTodoSubtask($todoId, ['id' => $id, 'title' => $title, 'created_by' => currentUser()]); + echo json_encode(['success' => true, 'id' => $id]); + } catch (\Throwable $e) { + http_response_code(500); + echo json_encode(['error' => 'Virhe: ' . $e->getMessage()]); + } + break; + + case 'todo_subtask_toggle': + requireAuth(); + requireCompany(); + if ($method !== 'POST') { echo json_encode(['error' => 'POST required']); break; } + try { + $input = json_decode(file_get_contents('php://input'), true); + $subtaskId = $input['id'] ?? ''; + if (!$subtaskId) { echo json_encode(['error' => 'id vaaditaan']); break; } + $completed = dbToggleTodoSubtask($subtaskId); + echo json_encode(['success' => true, 'completed' => $completed]); + } catch (\Throwable $e) { + http_response_code(500); + echo json_encode(['error' => 'Virhe: ' . $e->getMessage()]); + } + break; + + case 'todo_subtask_delete': + requireAuth(); + requireCompany(); + if ($method !== 'POST') { echo json_encode(['error' => 'POST required']); break; } + try { + $input = json_decode(file_get_contents('php://input'), true); + $subtaskId = $input['id'] ?? ''; + if (!$subtaskId) { echo json_encode(['error' => 'id vaaditaan']); break; } + $rows = _dbFetchAll("SELECT created_by FROM todo_subtasks WHERE id = ?", [$subtaskId]); + if (!empty($rows) && ($rows[0]['created_by'] === currentUser() || isCompanyAdmin())) { + dbDeleteTodoSubtask($subtaskId); + echo json_encode(['success' => true]); + } else { + http_response_code(403); + echo json_encode(['error' => 'Ei oikeutta']); + } + } catch (\Throwable $e) { + http_response_code(500); + echo json_encode(['error' => 'Virhe: ' . $e->getMessage()]); + } + break; + // ---------- ARCHIVE ---------- case 'archived_customers': requireAuth(); diff --git a/db.php b/db.php index 4f65fbb..e48d0a9 100644 --- a/db.php +++ b/db.php @@ -490,6 +490,18 @@ function initDatabase(): void { INDEX idx_todo (todo_id), INDEX idx_user (user) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", + + "CREATE TABLE IF NOT EXISTS todo_subtasks ( + id VARCHAR(20) PRIMARY KEY, + todo_id VARCHAR(20) NOT NULL, + title VARCHAR(500) NOT NULL, + completed TINYINT(1) DEFAULT 0, + sort_order INT DEFAULT 0, + created_by VARCHAR(100) DEFAULT '', + luotu DATETIME, + FOREIGN KEY (todo_id) REFERENCES todos(id) ON DELETE CASCADE, + INDEX idx_todo (todo_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", ]; foreach ($tables as $i => $sql) { @@ -1495,7 +1507,9 @@ function dbLoadTodos(string $companyId): array { $rows = _dbFetchAll(" SELECT t.*, COALESCE((SELECT SUM(te.hours) FROM todo_time_entries te WHERE te.todo_id = t.id), 0) AS total_hours, - (SELECT COUNT(*) FROM todo_comments tc WHERE tc.todo_id = t.id) AS comment_count + (SELECT COUNT(*) FROM todo_comments tc WHERE tc.todo_id = t.id) AS comment_count, + (SELECT COUNT(*) FROM todo_subtasks ts WHERE ts.todo_id = t.id) AS subtask_count, + (SELECT COUNT(*) FROM todo_subtasks ts2 WHERE ts2.todo_id = t.id AND ts2.completed = 1) AS subtask_done FROM todos t WHERE t.company_id = ? ORDER BY @@ -1506,6 +1520,8 @@ function dbLoadTodos(string $companyId): array { foreach ($rows as &$r) { $r['total_hours'] = floatval($r['total_hours']); $r['comment_count'] = intval($r['comment_count']); + $r['subtask_count'] = intval($r['subtask_count']); + $r['subtask_done'] = intval($r['subtask_done']); unset($r['company_id']); } return $rows; @@ -1517,9 +1533,13 @@ function dbLoadTodo(string $todoId): ?array { $todo = $row[0]; $todo['comments'] = _dbFetchAll("SELECT * FROM todo_comments WHERE todo_id = ? ORDER BY luotu", [$todoId]); $todo['time_entries'] = _dbFetchAll("SELECT * FROM todo_time_entries WHERE todo_id = ? ORDER BY work_date DESC, luotu DESC", [$todoId]); + $todo['subtasks'] = _dbFetchAll("SELECT * FROM todo_subtasks WHERE todo_id = ? ORDER BY sort_order, luotu", [$todoId]); foreach ($todo['time_entries'] as &$te) { $te['hours'] = floatval($te['hours']); } + foreach ($todo['subtasks'] as &$st) { + $st['completed'] = (bool)$st['completed']; + } $todo['total_hours'] = array_sum(array_column($todo['time_entries'], 'hours')); return $todo; } @@ -1583,3 +1603,25 @@ function dbAddTodoTimeEntry(string $todoId, array $entry): void { function dbDeleteTodoTimeEntry(string $entryId): void { _dbExecute("DELETE FROM todo_time_entries WHERE id = ?", [$entryId]); } + +function dbAddTodoSubtask(string $todoId, array $subtask): void { + $maxOrder = _dbFetchAll("SELECT COALESCE(MAX(sort_order), 0) + 1 AS next_order FROM todo_subtasks WHERE todo_id = ?", [$todoId]); + _dbExecute("INSERT INTO todo_subtasks (id, todo_id, title, completed, sort_order, created_by, luotu) VALUES (?, ?, ?, 0, ?, ?, ?)", [ + $subtask['id'] ?? generateId(), + $todoId, + $subtask['title'] ?? '', + $maxOrder[0]['next_order'] ?? 0, + $subtask['created_by'] ?? '', + date('Y-m-d H:i:s') + ]); +} + +function dbToggleTodoSubtask(string $subtaskId): bool { + _dbExecute("UPDATE todo_subtasks SET completed = NOT completed WHERE id = ?", [$subtaskId]); + $row = _dbFetchAll("SELECT completed FROM todo_subtasks WHERE id = ?", [$subtaskId]); + return !empty($row) && $row[0]['completed']; +} + +function dbDeleteTodoSubtask(string $subtaskId): void { + _dbExecute("DELETE FROM todo_subtasks WHERE id = ?", [$subtaskId]); +} diff --git a/index.html b/index.html index 7ae9699..dda2849 100644 --- a/index.html +++ b/index.html @@ -505,6 +505,16 @@
+ +