diff --git a/index.php b/index.php index e9967df..5a444f3 100644 --- a/index.php +++ b/index.php @@ -187,6 +187,53 @@ if ($action === 'api') { exit; } + if($api_action === 'add_task_tag') { + if(!isset($_SESSION['user_id'])) die(json_encode(['success'=>false,'error'=>'unauthorized'])); + $task_id = $_POST['task_id'] ?? null; + $tag = trim($_POST['tag'] ?? ''); + $user_id = $_SESSION['user_id']; + + if($task_id && $tag) { + $stmt = $pdo->prepare("SELECT tags FROM tasks WHERE id = ?"); + $stmt->execute([$task_id]); + $row = $stmt->fetch(); + $current_tags = $row ? ($row['tags'] ?? '') : ''; + + $tags_array = $current_tags ? array_filter(array_map('trim', explode(',', $current_tags))) : []; + if(!in_array($tag, $tags_array)) { + $tags_array[] = $tag; + $new_tags = implode(',', $tags_array); + $pdo->prepare("UPDATE tasks SET tags = ? WHERE id = ?")->execute([$new_tags, $task_id]); + } + echo json_encode(['success'=>true]); + } else { + echo json_encode(['success'=>false,'error'=>'Missing task_id or tag']); + } + exit; + } + + if($api_action === 'remove_task_tag') { + if(!isset($_SESSION['user_id'])) die(json_encode(['success'=>false,'error'=>'unauthorized'])); + $task_id = $_POST['task_id'] ?? null; + $tag = trim($_POST['tag'] ?? ''); + + if($task_id && $tag) { + $stmt = $pdo->prepare("SELECT tags FROM tasks WHERE id = ?"); + $stmt->execute([$task_id]); + $row = $stmt->fetch(); + $current_tags = $row ? ($row['tags'] ?? '') : ''; + + $tags_array = $current_tags ? array_filter(array_map('trim', explode(',', $current_tags))) : []; + $tags_array = array_diff($tags_array, [$tag]); + $new_tags = implode(',', $tags_array); + $pdo->prepare("UPDATE tasks SET tags = ? WHERE id = ?")->execute([$new_tags, $task_id]); + echo json_encode(['success'=>true]); + } else { + echo json_encode(['success'=>false,'error'=>'Missing task_id or tag']); + } + exit; + } + echo json_encode(['status'=>'error','msg'=>'Unknown api_action']); exit; } diff --git a/views/tasks.php b/views/tasks.php index 234f166..db5677a 100644 --- a/views/tasks.php +++ b/views/tasks.php @@ -194,7 +194,7 @@ $custom_like = $custom_tokens ? implode(' OR ', array_fill(0, count($custom_toke $params = array_merge([$user_id], $my_group_ids, $my_group_ids, [$user_id], $custom_tokens, $custom_tokens); -$sql = "SELECT t.*, u.username as assignor_user_name, g.name as assignor_group_name, au.username as assignee_user_name, ag.name as assignee_group_name, cu.username as completed_by_username, aru.username as archived_by_username +$sql = "SELECT t.*, u.username as assignor_user_name, u.profile_pic as assignor_profile_pic, g.name as assignor_group_name, au.username as assignee_user_name, au.profile_pic as assignee_profile_pic, ag.name as assignee_group_name, cu.username as completed_by_username, aru.username as archived_by_username FROM tasks t LEFT JOIN users u ON t.assignor_id = u.id LEFT JOIN groups g ON t.assignor_group_id = g.id @@ -275,7 +275,7 @@ usort($kanban['pending'], function($a, $b) { .kanban-task { background: var(--surface); padding: 1rem; border-radius: 10px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05), 0 2px 4px -1px rgba(0,0,0,0.03); - border: 1px solid rgba(0,0,0,0.05); cursor: grab; transition: transform 0.2s, box-shadow 0.2s, opacity 0.2s; + border: 1px solid rgba(0,0,0,0.05); transition: transform 0.2s, box-shadow 0.2s, opacity 0.2s; border-left: 4px solid var(--border); } body.dark-mode .kanban-task { border: 1px solid rgba(255,255,255,0.05); border-left: 4px solid var(--primary); } @@ -288,7 +288,7 @@ body.dark-mode .kanban-task { border: 1px solid rgba(255,255,255,0.05); border-l .kanban-task:hover:not(.expanded) { transform: translateY(-2px); box-shadow: 0 10px 15px -3px rgba(0,0,0,0.05); } .kanban-task.dragging { opacity: 0.4; transform: scale(1.02); box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1), 0 10px 10px -5px rgba(0,0,0,0.04); cursor: grabbing; z-index: 100; } -.kanban-task-header { cursor: pointer; display: flex; flex-direction: column; gap: 0.5rem; } +.kanban-task-header { cursor: grab; display: flex; flex-direction: column; gap: 0.5rem; } .kanban-task-title { font-weight: 700; font-size: 1.05rem; color: var(--text); /* Clamp to 2 lines when collapsed */ @@ -306,7 +306,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); + display: none; padding-top: 0.75rem; margin-top: 0.5rem; border-top: 1px dashed var(--border); position: relative; } .kanban-task.expanded .kanban-task-body { display: block; } .chevron { transition: transform 0.2s; font-size: 0.8rem; color: var(--text-light); } @@ -325,6 +325,8 @@ body.dark-mode .kanban-task { border: 1px solid rgba(255,255,255,0.05); border-l .kanban-task:not(.expanded) .expanded-only { display: none !important; } .kanban-task.expanded .avatar-collapsed-only { display: none !important; } .kanban-task.expanded .expanded-only { display: inline-flex !important; } +/* Hide header metadata row when expanded to avoid duplication */ +.kanban-task.expanded .kanban-task-header > div:last-child { display: none !important; } /* Custom Autocomplete Dropdown */ .autocomplete-dropdown { @@ -499,9 +501,9 @@ body.dark-mode .kanban-task { border: 1px solid rgba(255,255,255,0.05); border-l