Dataview Table with Toggleable Task Status

What I’m trying to do

I have a projects page where I list where I list down all of my active ongoing projects. Here I’d also like to have a table that lists down all active tasks in all of the related projects and that table should contain a column that allows me to to toggle the status of said tasks from the table without having to go to the notes where those tasks are in.

Things I have tried

I’ve tried using ChatGPT to write some dataviewjs code to try and achieve through the Tasks API, and while I can get the table to render, the toggle itself is useless and is basically there for aesthetics. The code I used is this:

// get all pages that might include tasks
const pages = dv.pages().where(p => p.file && p.file.tasks && p.file.tasks.length > 0);
console.log("Test");
  
// helper function to determine priority colour
function priorityColor(priority) {
  if (!priority) return "black";
  const p = priority.toLowerCase();
  if (p === "high") return "red";
  if (p === "medium") return "orange";
  if (p === "low") return "blue";
  return "black";
}
  
// async function to update the task's status using the Tasks API
async function updateTaskCompletion(file, task, newCompleted) {
  try {
    // Get the Tasks plugin API
    const tasksApi = app.plugins.plugins['obsidian-tasks-plugin'].apiV1;
    console.log("Tasks API:", tasksApi);
    
    // Read the current content to get the exact task line
    const content = await app.vault.read(file);
    const lines = content.split("\n");
    const taskLine = lines[task.line]; // Get the full line content
    console.log("Current task line:", taskLine);
    
    // Toggle the task using the Tasks API
    const result = await tasksApi.executeToggleTaskDoneCommand(taskLine, file.path);
    console.log("Toggled markdown:", result);
    // If the Tasks API didn't work, fall back to manual file modification
    if (!result) {
      if (newCompleted) {
        lines[task.line] = lines[task.line].replace(/- \[ \]/, "- [x]");
      } else {
        lines[task.line] = lines[task.line].replace(/- \[x\]/, "- [ ]");
      }
      const newContent = lines.join("\n");
      await app.vault.modify(file, newContent);
    }
    
    return true;
  } catch (error) {
    console.error("Failed to update task:", error);
    
    // Fallback to the original method if Tasks API isn't available
    try {
      // read the current content of the file
      let content = await app.vault.read(file);
      const lines = content.split("\n");
      const index = task.line;
      if (index < lines.length) {
        if (newCompleted) {
          lines[index] = lines[index].replace(/- \[ \]/, "- [x]");
        } else {
          lines[index] = lines[index].replace(/- \[x\]/, "- [ ]");
        }
      }
      const newContent = lines.join("\n");
      await app.vault.modify(file, newContent);
      return true;
    } catch (e) {
      console.error("Fallback update failed:", e);
      return false;
    }
  }
}
  
// function to render a checkbox as a DOM element that toggles its state and updates the file
function renderCheckbox(task, page, rowElement) {
  const wrapper = document.createElement("span");
  wrapper.className = "custom-checkbox-wrapper";
  const checkbox = document.createElement("input");
  checkbox.type = "checkbox";
  checkbox.checked = task.completed;
  checkbox.className = "custom-checkbox";
  checkbox.style.cursor = "pointer";
  
  // Apply styling for completed tasks
  if (task.completed) {
    wrapper.classList.add("custom-checkbox-complete");
  }
  
  checkbox.addEventListener("click", async (e) => {
    e.stopPropagation();
    const newCompleted = !task.completed;
    
    // Display a small loading indicator
    checkbox.disabled = true;
    
    // Update the file with the new task status using Tasks API
    const success = await updateTaskCompletion(page.file, task, newCompleted);
    
    if (success) {
      // Update the in-memory task state
      task.completed = newCompleted;
      
      // Update the status cell text
      if (rowElement.cells && rowElement.cells[3]) {
        rowElement.cells[3].innerText = task.completed ? "complete" : "incomplete";
      }
      
      // Update the checkbox styling
      if (task.completed) {
        wrapper.classList.add("custom-checkbox-complete");
      } else {
        wrapper.classList.remove("custom-checkbox-complete");
      }
    }
    
    checkbox.disabled = false;
  });
  
  wrapper.appendChild(checkbox);
  return wrapper;
}
  
// create an array to accumulate rows
let rows = [];
  
// iterate over pages and their tasks
pages.forEach(page => {
  page.file.tasks.forEach(task => {
    // Create an object to store row info for later reference
    const rowElement = { cells: [] };
    
    // retrieve priority and due date if set in the task object or page frontmatter
    const priority = task.priority || (page.priority ? page.priority : "low");
    const dueDate = task.due 
      ? moment(task.due).format("YYYY-MM-DD") 
      : (page.due ? moment(page.due).format("YYYY-MM-DD") : "");
    
    // the icon column: display a note icon linked to the note
    const iconLink = `<a href="${page.file.path}">📄</a>`;
    
    // build the row for the table
    const row = [
      renderCheckbox(task, page, rowElement),
      iconLink,
      task.text,
      task.completed ? "complete" : "incomplete",
      `<span style="color: ${priorityColor(priority)}">${priority}</span>`,
      dueDate
    ];
    
    // Store reference to cells for later updates
    rowElement.cells = row;
    rows.push(row);
  });
});
  
// custom header names for the table columns
const headers = ["Done", "Note", "Task", "Status", "Priority", "Due Date"];
  
// render the table using the dataview API
dv.table(headers, rows);