diff --git a/index.php b/index.php index c6b4ce5..b2dfe5e 100644 --- a/index.php +++ b/index.php @@ -7,14 +7,142 @@ $action = (string)($_GET['action'] ?? 'dashboard'); // API updates for task kanban drag n drop if ($action === 'api') { $api_action = $_GET['api_action'] ?? ''; - if(!isset($_SESSION['user_id'])) die(json_encode(['status'=>'error','msg'=>'unauthorized'])); + header('Content-Type: application/json; charset=utf-8'); + + // Ensure newer task columns exist (API might be called without visiting the tasks page first). + 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 custom_assignee_name TEXT"); } 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) {} + + $format_dt = function($value) { + $value = is_string($value) ? trim($value) : $value; + if(!$value) return null; + $ts = strtotime((string)$value); + if($ts === false) return null; + return date('Y-m-d H:i', $ts); + }; + + $format_status = function($value) { + if(!$value) return null; + $s = strtolower(trim((string)$value)); + if($s === 'in_progress') return 'In Progress'; + $s = str_replace('_', ' ', $s); + return ucfirst($s); + }; + if($api_action === 'update_task_status') { + if(!isset($_SESSION['user_id'])) die(json_encode(['status'=>'error','msg'=>'unauthorized'])); $task_id = $_POST['task_id'] ?? null; $status = $_POST['status'] ?? null; $stmt = $pdo->prepare("UPDATE tasks SET status = ?, status_updated_at = CURRENT_TIMESTAMP WHERE id = ?"); $stmt->execute([$status, $task_id]); echo json_encode(['status'=>'ok']); + exit; } + + if($api_action === 'get_tasks_status') { + $task_id = $_GET['task_id'] ?? null; + + // Correlated subquery gives attachment count per task. + $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, + (SELECT COUNT(*) FROM task_attachments ta WHERE ta.task_id = t.id) AS attachments_count + FROM tasks t + LEFT JOIN users u ON t.assignor_id = u.id + LEFT JOIN groups g ON t.assignor_group_id = g.id + LEFT JOIN users au ON (t.assignee_type='user' AND t.assignee_id = au.id) + LEFT JOIN groups ag ON (t.assignee_type='group' AND t.assignee_id = ag.id) + "; + $params = []; + + if($task_id !== null && $task_id !== '') { + if(!ctype_digit((string)$task_id)) { + echo json_encode(['status'=>'error','msg'=>'Invalid task_id']); + exit; + } + $sql .= " WHERE t.id = ? LIMIT 1"; + $params[] = (int)$task_id; + } else { + $sql .= " ORDER BY t.created_at DESC"; + } + + $stmt = $pdo->prepare($sql); + $stmt->execute($params); + $rows = $stmt->fetchAll(); + + if($task_id !== null && $task_id !== '') { + $row = $rows ? $rows[0] : null; + if(!$row) { + echo json_encode(['status'=>'error','msg'=>'Task not found']); + exit; + } + + $from = null; + if(($row['assignor_type'] ?? null) === 'group') $from = $row['assignor_group_name'] ?? null; + elseif(($row['assignor_type'] ?? null) === 'user') $from = $row['assignor_user_name'] ?? null; + + $to = null; + if(($row['assignee_type'] ?? null) === 'user') $to = $row['assignee_user_name'] ?? null; + elseif(($row['assignee_type'] ?? null) === 'group') $to = $row['assignee_group_name'] ?? null; + elseif(($row['assignee_type'] ?? null) === 'custom') $to = $row['custom_assignee_name'] ?? null; + + $desc = (string)($row['description'] ?? ''); + $desc = trim($desc) !== '' ? $desc : (string)($row['title'] ?? ''); + + $payload = [ + 'task_id' => (string)$row['id'], + 'task_created_date' => $format_dt($row['created_at'] ?? 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), + ]; + + echo json_encode($payload); + exit; + } + + $result = []; + foreach($rows as $row) { + $from = null; + if(($row['assignor_type'] ?? null) === 'group') $from = $row['assignor_group_name'] ?? null; + elseif(($row['assignor_type'] ?? null) === 'user') $from = $row['assignor_user_name'] ?? null; + + $to = null; + if(($row['assignee_type'] ?? null) === 'user') $to = $row['assignee_user_name'] ?? null; + elseif(($row['assignee_type'] ?? null) === 'group') $to = $row['assignee_group_name'] ?? null; + elseif(($row['assignee_type'] ?? null) === 'custom') $to = $row['custom_assignee_name'] ?? null; + + $desc = (string)($row['description'] ?? ''); + $desc = trim($desc) !== '' ? $desc : (string)($row['title'] ?? ''); + + $result[] = [ + 'task_id' => (string)$row['id'], + 'task_created_date' => $format_dt($row['created_at'] ?? 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), + ]; + } + + echo json_encode($result); + exit; + } + + echo json_encode(['status'=>'error','msg'=>'Unknown api_action']); exit; } diff --git a/views/tasks.php b/views/tasks.php index b2f0fea..00de79f 100644 --- a/views/tasks.php +++ b/views/tasks.php @@ -176,7 +176,17 @@ $all_g = query("SELECT id, name FROM groups")->fetchAll(); $group_in = implode(',', array_fill(0, count($my_group_ids), '?')) ?: '0'; -$params = array_merge([$user_id], $my_group_ids, $my_group_ids, [$user_id]); +$custom_tokens = []; +if(!empty($current_user['username'])) $custom_tokens[] = (string)$current_user['username']; +foreach($user_groups as $g) { + if(!empty($g['name'])) $custom_tokens[] = (string)$g['name']; +} +$custom_tokens = array_values(array_unique(array_filter(array_map('trim', $custom_tokens), fn($v) => $v !== ''))); + +$custom_in = implode(',', array_fill(0, count($custom_tokens), '?')) ?: '0'; +$custom_like = $custom_tokens ? implode(' OR ', array_fill(0, count($custom_tokens), "(','||lower(replace(t.custom_assignee_name,' ',''))||',') LIKE ('%,'||lower(replace(?,' ',''))||',%')")) : '0'; + +$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 FROM tasks t @@ -188,6 +198,14 @@ $sql = "SELECT t.*, u.username as assignor_user_name, g.name as assignor_group_n OR (t.assignee_type='group' AND t.assignee_id IN ($group_in)) OR (t.assignor_type='group' AND t.assignor_group_id IN ($group_in)) OR (t.assignor_type='user' AND t.assignor_id = ?) + OR ( + COALESCE(t.assignee_type,'custom')='custom' + AND t.custom_assignee_name IS NOT NULL + AND ( + t.custom_assignee_name IN ($custom_in) + OR ($custom_like) + ) + ) ORDER BY t.created_at DESC"; $stmt = $pdo->prepare($sql); $stmt->execute($params);