Uusi TODO-moduuli: Tehtävät + Kehitysehdotukset + Ajanseuranta

Talon sisäinen tehtävienhallinta kahdella alatabilla:

Tehtävät (admin luo):
- Prioriteetti (normaali/tärkeä/kiireellinen), status, deadline
- Vastuuhenkilö-osoitus, inline-muokkaus lukunäkymässä
- Aikakirjaukset: pvm, tunnit, kuvaus - kaikki voivat kirjata
- Myöhästyneet = punainen reunus, lähestyvät = keltainen
- Kommentointi kaikille käyttäjille

Kehitysehdotukset (kaikki voivat luoda):
- Status: ehdotettu → harkinnassa → toteutettu/hylätty (admin muuttaa)
- Kommentointi kaikille
- Ehdottaja voi muokata omia

Tietokanta: 3 taulua (todos, todo_comments, todo_time_entries)
API: 10 endpointtia oikeustarkistuksineen
Frontend: Sub-tab navigointi, kortti-grid, 3-näkymämalli per alatabi

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 13:14:53 +02:00
parent ec86263c5c
commit 4a1dccb6ff
5 changed files with 958 additions and 2 deletions

138
db.php
View File

@@ -445,6 +445,50 @@ function initDatabase(): void {
INDEX idx_company (company_id),
INDEX idx_category (category_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
"CREATE TABLE IF NOT EXISTS todos (
id VARCHAR(20) PRIMARY KEY,
company_id VARCHAR(50) NOT NULL,
type VARCHAR(20) NOT NULL DEFAULT 'task',
title VARCHAR(500) NOT NULL,
description TEXT,
status VARCHAR(30) NOT NULL DEFAULT 'avoin',
priority VARCHAR(20) DEFAULT 'normaali',
assigned_to VARCHAR(100) DEFAULT '',
created_by VARCHAR(100) NOT NULL DEFAULT '',
deadline DATE DEFAULT NULL,
luotu DATETIME,
muokattu DATETIME NULL,
muokkaaja VARCHAR(100) DEFAULT '',
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE,
INDEX idx_company (company_id),
INDEX idx_type (type),
INDEX idx_status (status),
INDEX idx_deadline (deadline)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
"CREATE TABLE IF NOT EXISTS todo_comments (
id VARCHAR(20) PRIMARY KEY,
todo_id VARCHAR(20) NOT NULL,
author VARCHAR(100) NOT NULL,
body TEXT NOT NULL,
luotu DATETIME,
FOREIGN KEY (todo_id) REFERENCES todos(id) ON DELETE CASCADE,
INDEX idx_todo (todo_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
"CREATE TABLE IF NOT EXISTS todo_time_entries (
id VARCHAR(20) PRIMARY KEY,
todo_id VARCHAR(20) NOT NULL,
user VARCHAR(100) NOT NULL,
hours DECIMAL(6,2) NOT NULL,
description VARCHAR(500) DEFAULT '',
work_date DATE NOT NULL,
luotu DATETIME,
FOREIGN KEY (todo_id) REFERENCES todos(id) ON DELETE CASCADE,
INDEX idx_todo (todo_id),
INDEX idx_user (user)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
];
foreach ($tables as $i => $sql) {
@@ -1442,3 +1486,97 @@ function dbIsPriorityEmail(string $companyId, string $email): bool {
}
return false;
}
// ==================== TEHTÄVÄT (TODOS) ====================
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
FROM todos t
WHERE t.company_id = ?
ORDER BY
CASE t.priority WHEN 'kiireellinen' THEN 0 WHEN 'tarkea' THEN 1 ELSE 2 END,
t.deadline IS NULL, t.deadline ASC,
t.luotu DESC
", [$companyId]);
foreach ($rows as &$r) {
$r['total_hours'] = floatval($r['total_hours']);
$r['comment_count'] = intval($r['comment_count']);
unset($r['company_id']);
}
return $rows;
}
function dbLoadTodo(string $todoId): ?array {
$row = _dbFetchAll("SELECT * FROM todos WHERE id = ?", [$todoId]);
if (empty($row)) return null;
$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]);
foreach ($todo['time_entries'] as &$te) {
$te['hours'] = floatval($te['hours']);
}
$todo['total_hours'] = array_sum(array_column($todo['time_entries'], 'hours'));
return $todo;
}
function dbSaveTodo(string $companyId, array $todo): void {
_dbExecute("
INSERT INTO todos (id, company_id, type, title, description, status, priority, assigned_to, created_by, deadline, luotu, muokattu, muokkaaja)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
title = VALUES(title), description = VALUES(description),
status = VALUES(status), priority = VALUES(priority),
assigned_to = VALUES(assigned_to), deadline = VALUES(deadline),
muokattu = VALUES(muokattu), muokkaaja = VALUES(muokkaaja)
", [
$todo['id'], $companyId,
$todo['type'] ?? 'task',
$todo['title'] ?? '',
$todo['description'] ?? '',
$todo['status'] ?? 'avoin',
$todo['priority'] ?? 'normaali',
$todo['assigned_to'] ?? '',
$todo['created_by'] ?? '',
!empty($todo['deadline']) ? $todo['deadline'] : null,
$todo['luotu'] ?? date('Y-m-d H:i:s'),
date('Y-m-d H:i:s'),
$todo['muokkaaja'] ?? ''
]);
}
function dbDeleteTodo(string $todoId): void {
_dbExecute("DELETE FROM todos WHERE id = ?", [$todoId]);
}
function dbAddTodoComment(string $todoId, array $comment): void {
_dbExecute("INSERT INTO todo_comments (id, todo_id, author, body, luotu) VALUES (?, ?, ?, ?, ?)", [
$comment['id'] ?? generateId(),
$todoId,
$comment['author'] ?? '',
$comment['body'] ?? '',
$comment['luotu'] ?? date('Y-m-d H:i:s')
]);
}
function dbDeleteTodoComment(string $commentId): void {
_dbExecute("DELETE FROM todo_comments WHERE id = ?", [$commentId]);
}
function dbAddTodoTimeEntry(string $todoId, array $entry): void {
_dbExecute("INSERT INTO todo_time_entries (id, todo_id, user, hours, description, work_date, luotu) VALUES (?, ?, ?, ?, ?, ?, ?)", [
$entry['id'] ?? generateId(),
$todoId,
$entry['user'] ?? '',
$entry['hours'] ?? 0,
$entry['description'] ?? '',
$entry['work_date'] ?? date('Y-m-d'),
$entry['luotu'] ?? date('Y-m-d H:i:s')
]);
}
function dbDeleteTodoTimeEntry(string $entryId): void {
_dbExecute("DELETE FROM todo_time_entries WHERE id = ?", [$entryId]);
}