diff --git a/index.php b/index.php index 5a444f3..c432e94 100644 --- a/index.php +++ b/index.php @@ -1,5 +1,6 @@ exec("ALTER TABLE tasks ADD COLUMN assignor_type TEXT DEFAULT 'user'"); } catch(Exception $e) {} try { $pdo->exec("ALTER TABLE tasks ADD COLUMN assignor_group_id INTEGER"); } catch(Exception $e) {} try { $pdo->exec("ALTER TABLE tasks ADD COLUMN status_updated_at DATETIME"); } catch(Exception $e) {} - try { $pdo->exec("ALTER TABLE tasks ADD COLUMN due_date DATE"); } catch(Exception $e) {} + try { $pdo->exec("ALTER TABLE tasks ADD COLUMN due_date DATETIME"); } catch(Exception $e) {} try { $pdo->exec("ALTER TABLE tasks ADD COLUMN custom_assignee_name TEXT"); } catch(Exception $e) {} try { $pdo->exec("ALTER TABLE tasks ADD COLUMN completed_by INTEGER DEFAULT NULL"); } catch(Exception $e) {} try { $pdo->exec("ALTER TABLE tasks ADD COLUMN archived_by INTEGER DEFAULT NULL"); } catch(Exception $e) {} @@ -99,7 +100,16 @@ if ($action === 'api') { g.name as assignor_group_name, au.username as assignee_user_name, ag.name as assignee_group_name, - (SELECT COUNT(*) FROM task_attachments ta WHERE ta.task_id = t.id) AS attachments_count + (SELECT COUNT(*) FROM task_attachments ta WHERE ta.task_id = t.id) AS attachments_count, + (SELECT COUNT(*) FROM task_comments tc WHERE tc.task_id = t.id) AS comments_count, + (SELECT comment FROM task_comments tc WHERE tc.task_id = t.id ORDER BY tc.created_at DESC LIMIT 1) AS last_comment, + (SELECT created_at FROM task_comments tc WHERE tc.task_id = t.id ORDER BY tc.created_at DESC LIMIT 1) AS last_comment_date, + (SELECT cu.username FROM task_comments tc JOIN users cu ON tc.created_by = cu.id WHERE tc.task_id = t.id ORDER BY tc.created_at DESC LIMIT 1) AS last_commenter, + t.status_updated_at, + t.completed_by, + t.archived_by, + t.tags, + t.title FROM tasks t LEFT JOIN users u ON t.assignor_id = u.id LEFT JOIN groups g ON t.assignor_group_id = g.id @@ -142,15 +152,35 @@ if ($action === 'api') { $desc = (string)($row['description'] ?? ''); $desc = trim($desc) !== '' ? $desc : (string)($row['title'] ?? ''); + $tags = !empty($row['tags']) ? array_filter(array_map('trim', explode(',', $row['tags']))) : []; + + // Logic for last_modified_by + // If it's archived, 'Archived by' user; if completed, 'Completed by' user; otherwise latest commenter or creator. + $last_mod_by = 'System'; + if ($row['status'] === 'archived' && $row['archived_by']) { + $last_mod_by = query("SELECT username FROM users WHERE id=?", [$row['archived_by']])->fetchColumn() ?: 'System'; + } elseif ($row['status'] === 'completed' && $row['completed_by']) { + $last_mod_by = query("SELECT username FROM users WHERE id=?", [$row['completed_by']])->fetchColumn() ?: 'System'; + } else { + $last_mod_by = $row['last_commenter'] ?: $row['assignor_user_name']; + } + $payload = [ 'task_id' => (string)$row['id'], - 'task_created_date' => $format_dt($row['created_at'] ?? null), + 'task_name' => (string)$row['title'], + 'task_created_date' => $format_dt($row['created_at'] ? $row['created_at'] . ' UTC' : null), 'from' => $from, 'to' => $to, 'task_description' => $desc, 'due_date' => $format_dt($row['due_date'] ?? null), 'current_status' => $format_status($row['status'] ?? null), 'attachments_count' => (int)($row['attachments_count'] ?? 0), + 'tags_count' => count($tags), + 'comments_count' => (int)($row['comments_count'] ?? 0), + 'last_modified_by' => $last_mod_by, + 'last_modified_date' => $format_dt(($row['status_updated_at'] ?: $row['created_at']) . ' UTC'), + 'last_comment' => $row['last_comment'] ?: null, + 'last_comment_date' => $format_dt($row['last_comment_date'] ? $row['last_comment_date'] . ' UTC' : null), ]; echo json_encode($payload); @@ -171,15 +201,33 @@ if ($action === 'api') { $desc = (string)($row['description'] ?? ''); $desc = trim($desc) !== '' ? $desc : (string)($row['title'] ?? ''); + $tags = !empty($row['tags']) ? array_filter(array_map('trim', explode(',', $row['tags']))) : []; + + $last_mod_by = 'System'; + if ($row['status'] === 'archived' && $row['archived_by']) { + $last_mod_by = query("SELECT username FROM users WHERE id=?", [$row['archived_by']])->fetchColumn() ?: 'System'; + } elseif ($row['status'] === 'completed' && $row['completed_by']) { + $last_mod_by = query("SELECT username FROM users WHERE id=?", [$row['completed_by']])->fetchColumn() ?: 'System'; + } else { + $last_mod_by = $row['last_commenter'] ?: $row['assignor_user_name']; + } + $result[] = [ 'task_id' => (string)$row['id'], - 'task_created_date' => $format_dt($row['created_at'] ?? null), + 'task_name' => (string)$row['title'], + 'task_created_date' => $format_dt($row['created_at'] ? $row['created_at'] . ' UTC' : null), 'from' => $from, 'to' => $to, 'task_description' => $desc, 'due_date' => $format_dt($row['due_date'] ?? null), 'current_status' => $format_status($row['status'] ?? null), 'attachments_count' => (int)($row['attachments_count'] ?? 0), + 'tags_count' => count($tags), + 'comments_count' => (int)($row['comments_count'] ?? 0), + 'last_modified_by' => $last_mod_by, + 'last_modified_date' => $format_dt(($row['status_updated_at'] ?: $row['created_at']) . ' UTC'), + 'last_comment' => $row['last_comment'] ?: null, + 'last_comment_date' => $format_dt($row['last_comment_date'] ? $row['last_comment_date'] . ' UTC' : null), ]; } diff --git a/views/layout_top.php b/views/layout_top.php index 2f7adfe..5b31d20 100644 --- a/views/layout_top.php +++ b/views/layout_top.php @@ -9,12 +9,24 @@ + + + query("SELECT label_name as tag FROM notes WHERE label_name IS NOT NULL AND label_name != ''")->fetchAll(); diff --git a/views/tasks.php b/views/tasks.php index db5677a..8aa9c49 100644 --- a/views/tasks.php +++ b/views/tasks.php @@ -3,7 +3,7 @@ try { $pdo->exec("ALTER TABLE tasks ADD COLUMN assignor_type TEXT DEFAULT 'user'"); } catch(Exception $e) {} try { $pdo->exec("ALTER TABLE tasks ADD COLUMN assignor_group_id INTEGER"); } catch(Exception $e) {} try { $pdo->exec("ALTER TABLE tasks ADD COLUMN status_updated_at DATETIME"); } catch(Exception $e) {} -try { $pdo->exec("ALTER TABLE tasks ADD COLUMN due_date DATE"); } catch(Exception $e) {} +try { $pdo->exec("ALTER TABLE tasks ADD COLUMN due_date DATETIME"); } catch(Exception $e) {} try { $pdo->exec("ALTER TABLE tasks ADD COLUMN tags TEXT DEFAULT NULL"); } catch(Exception $e) {} try { $pdo->exec("CREATE TABLE IF NOT EXISTS task_attachments (id INTEGER PRIMARY KEY AUTOINCREMENT, task_id INTEGER, file_name TEXT, file_path TEXT, owner_id INTEGER, created_at DATETIME DEFAULT CURRENT_TIMESTAMP)"); } catch(Exception $e) {} try { @@ -237,6 +237,26 @@ if($t_ids) { } } +// Pre-fetch profile pics for all potential assignees in the view +$all_usernames = []; +foreach($tasks as $t) { + if($t['assignee_type'] == 'user' || $t['assignee_type'] == 'custom') { + $names = array_filter(array_map('trim', explode(',', $t['assignee_user_name'] ?? $t['custom_assignee_name'] ?? ''))); + foreach($names as $n) $all_usernames[] = $n; + } + if($t['assignor_type'] == 'user') $all_usernames[] = $t['assignor_user_name']; +} +$user_pics = []; +if($all_usernames) { + $all_usernames = array_unique($all_usernames); + $in_u = str_repeat('?,', count($all_usernames) - 1) . '?'; + $stmt_u = $pdo->prepare("SELECT username, profile_pic FROM users WHERE username IN ($in_u)"); + $stmt_u->execute(array_values($all_usernames)); + foreach($stmt_u->fetchAll() as $row) { + $user_pics[$row['username']] = $row['profile_pic']; + } +} + $kanban = ['pending' => [], 'in_progress' => [], 'completed' => [], 'archived' => []]; foreach($tasks as $t) { if(isset($kanban[$t['status']])) $kanban[$t['status']][] = $t; @@ -246,7 +266,7 @@ foreach($tasks as $t) { usort($kanban['pending'], function($a, $b) { $da = $a['due_date'] ? strtotime($a['due_date']) : PHP_INT_MAX; $db = $b['due_date'] ? strtotime($b['due_date']) : PHP_INT_MAX; - if($da == $db) return strtotime($b['created_at']) - strtotime($a['created_at']); + if($da == $db) return strtotime($b['created_at'] . ' UTC') - strtotime($a['created_at'] . ' UTC'); return $da - $db; }); ?> @@ -306,7 +326,7 @@ body.dark-mode .kanban-task { border: 1px solid rgba(255,255,255,0.05); border-l -webkit-line-clamp: unset; } .kanban-task-body { - display: none; padding-top: 0.75rem; margin-top: 0.5rem; border-top: 1px dashed var(--border); position: relative; + display: none; padding-top: 0.75rem; margin-top: 0.5rem; border-top: 1px dashed var(--border); position: relative; padding-bottom: 2.5rem; } .kanban-task.expanded .kanban-task-body { display: block; } .chevron { transition: transform 0.2s; font-size: 0.8rem; color: var(--text-light); } @@ -445,11 +465,11 @@ body.dark-mode .kanban-task { border: 1px solid rgba(255,255,255,0.05); border-l -