Get It Done β€” Excel Spine
GET IT DONE
Synced to Sheets + Calendar
Synced
unsaved changes
Task Queue 0
πŸ• Version Historyexpand
No versions yet.
βš™οΈ Apps Script URL & Setupexpand
Web App URL
Apps Script Code β€” paste this into your script.google.com project
const TOKEN = 'excelspine2026';

function doGet(e) {
  const p = e && e.parameter;
  if (!p || p.token !== TOKEN) return json({ error: 'unauthorized' });
  const ss = SpreadsheetApp.getActiveSpreadsheet();

  if (p.action === 'tasks') {
    const gtdSheet = ss.getSheetByName('GTD');
    if (gtdSheet) {
      const raw = gtdSheet.getRange('A1').getValue();
      if (raw) { try { return json({ tasks: JSON.parse(raw) }); } catch(e) {} }
    }
    const taskSheet = ss.getSheetByName('Tasks');
    if (!taskSheet) return json({ tasks: [] });
    const data = taskSheet.getDataRange().getValues();
    if (data.length < 2) return json({ tasks: [] });
    const headers = data[0];
    const tasks = data.slice(1).map(row => {
      const t = {};
      headers.forEach((h, i) => { t[h] = row[i] || ''; });
      ['due_date','date_added'].forEach(f => {
        if (t[f] instanceof Date) {
          const d = t[f];
          t[f] = d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0');
        } else if (t[f] && String(t[f]).includes('GMT')) {
          const d = new Date(t[f]);
          if (!isNaN(d)) t[f] = d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0');
        }
      });
      if (t.due_time && String(t.due_time).includes('1899')) t.due_time = '';
      t.done = t.done === 'Yes' || t.done === true;
      t.calendarSynced = !!t.cal_event_id;
      return t;
    });
    return json({ tasks: tasks });
  }

  if (p.action === 'gtd_versions') {
    const sheet = ss.getSheetByName('GTD_Versions');
    if (!sheet) return json({ versions: [] });
    const data = sheet.getDataRange().getValues();
    if (data.length < 2) return json({ versions: [] });
    const versions = data.slice(1).map(row => ({ id: row[0], timestamp: row[1], label: row[2], snapshot: row[3] }));
    return json({ versions: versions });
  }
  return json({ error: 'unknown action' });
}

function doPost(e) {
  const body = JSON.parse(e.postData.contents);
  if (body.token !== TOKEN) return json({ error: 'unauthorized' });
  const ss = SpreadsheetApp.getActiveSpreadsheet();

  if (body.action === 'sync_tasks') {
    const sheet = ss.getSheetByName('GTD') || ss.insertSheet('GTD');
    sheet.getRange('A1').setValue(JSON.stringify(body.tasks));
    sheet.getRange('B1').setValue(new Date().toISOString());
    writeTasksTab(ss, body.tasks);
    return json({ status: 'ok' });
  }

  if (body.action === 'save_gtd_version') {
    const sheet = ss.getSheetByName('GTD_Versions') || ss.insertSheet('GTD_Versions');
    const v = body.version;
    sheet.appendRow([v.id, v.timestamp, v.label, v.snapshot]);
    return json({ status: 'ok' });
  }

  if (body.action === 'create_gtd_cal_event') {
    const task = body.task;
    if (!task || !task.due_date) return json({ error: 'no due_date' });
    const cal = CalendarApp.getDefaultCalendar();
    const start = new Date(task.due_date);
    const end = new Date(start.getTime() + 60 * 60 * 1000);
    const desc = (task.note || '') + '\n\n[GTD][id:' + task.id + ']';
    const event = cal.createEvent('[GTD] ' + task.title, start, end, { description: desc });
    return json({ status: 'ok', calEventId: event.getId() });
  }
  return json({ error: 'unknown action' });
}

function writeTasksTab(ss, tasks) {
  const tab = ss.getSheetByName('Tasks') || ss.insertSheet('Tasks');
  tab.clearContents();
  const headers = ['id','title','due_date','due_time','note','gtd_decision','date_added','cal_event_id','done'];
  const rows = [headers];
  (tasks||[]).forEach(t => {
    rows.push([t.id||'',t.title||'',t.due_date||'',t.due_time||'',t.note||'',t.gtd_decision||'',t.date_added||'',t.cal_event_id||'',t.done?'Yes':'No']);
  });
  tab.getRange(1,1,rows.length,headers.length).setValues(rows);
  tab.getRange(1,1,1,headers.length).setFontWeight('bold');
  tab.setFrozenRows(1);
}

function json(obj) {
  return ContentService.createTextOutput(JSON.stringify(obj)).setMimeType(ContentService.MimeType.JSON);
}
Deploy steps:
1. Open script.google.com β†’ open the GTD project
2. Replace all code with the above β†’ Save
3. Deploy β†’ Manage Deployments β†’ Edit β†’ New Version β†’ Deploy
4. Execute as: Me Β· Access: Anyone
5. Paste the Web App URL above β†’ hit Test

Schedule Task

β€”
Clear schedule
Excel Spine Dashboards