Tutorial – Crie Eventos no Google Agenda a Partir da sua Planilha

Neste guia passo a passo, você aprenderá a construir um sistema que cria uma ponte poderosa entre o Google Planilhas e o Google Agenda. Vamos desenvolver um painel de controle que lê uma lista de compromissos da sua planilha e, com um único clique, os transforma em eventos reais na sua agenda.

É a automação perfeita para garantir que tarefas e prazos registrados em suas planilhas nunca mais sejam esquecidos.

Preparação da Planilha:

Para começar, crie uma nova Planilha Google com uma aba chamada Agendamentos. A primeira linha desta aba deve conter exatamente os seguintes cabeçalhos: Título do Evento, Data Início, Hora Início, Data Fim, Hora Fim, Descrição, Local, Status.

Código.gs

// ==========================================================================
// ARQUIVO: Código.gs 
// ==========================================================================

const HTML_INDEX = 'Index';
const SHEET_AGENDAMENTOS = 'Agendamentos';
const HEADERS_AGENDAMENTOS = ["Título do Evento", "Data Início", "Hora Início", "Data Fim", "Hora Fim", "Descrição", "Local", "Status"];
const STATUS_TRIGGER = "Agendar";

function doGet(e) {
  setupSheet(SHEET_AGENDAMENTOS, HEADERS_AGENDAMENTOS);
  const tpl = HtmlService.createTemplateFromFile(HTML_INDEX);
  return tpl.evaluate()
    .addMetaTag('viewport', 'width=device-width, initial-scale=1.0')
    .setTitle('Sincronizador de Agenda');
}

function setupSheet(sheetName, headers) {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  let sheet = ss.getSheetByName(sheetName);
  if (!sheet) {
    sheet = ss.insertSheet(sheetName);
    sheet.appendRow(headers);
    sheet.setFrozenRows(1);
    sheet.getRange("B2:B").setNumberFormat("dd/mm/yyyy");
    sheet.getRange("D2:D").setNumberFormat("dd/mm/yyyy");
    sheet.getRange("C2:C").setNumberFormat("hh:mm");
    sheet.getRange("E2:E").setNumberFormat("hh:mm");
  }
}

function getAgendamentos() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_AGENDAMENTOS);
  const lastRow = sheet.getLastRow();
  if (lastRow < 2) return [];
  const values = sheet.getRange(2, 1, lastRow - 1, HEADERS_AGENDAMENTOS.length).getValues();
  return values.map(row => {
    let rowObject = {};
    HEADERS_AGENDAMENTOS.forEach((header, index) => {
      let value = row[index];
      if (value instanceof Date) {
        if (header.includes("Data")) value = Utilities.formatDate(value, Session.getScriptTimeZone(), "dd/MM/yyyy");
        else if (header.includes("Hora")) value = Utilities.formatDate(value, Session.getScriptTimeZone(), "HH:mm");
      }
      rowObject[header] = value;
    });
    return rowObject;
  });
}

/**
 * Sincroniza eventos e retorna um log detalhado.
 * @returns {object} Um resumo com contagens e um array de logs de erro.
 */
function syncEvents() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_AGENDAMENTOS);
  const calendar = CalendarApp.getDefaultCalendar();
  const data = sheet.getDataRange().getValues();
  const statusColIndex = HEADERS_AGENDAMENTOS.indexOf("Status");
  let successCount = 0;
  let errorLogs = []; 

  for (let i = 1; i < data.length; i++) {
    const row = data[i];
    if (row[statusColIndex] !== STATUS_TRIGGER) continue;

    const title = row[0];
    try {
      const startDate = row[1]; const startTime = row[2];
      const endDate = row[3];   const endTime = row[4];
      const description = row[5]; const location = row[6];
      
      if (!title || !startDate || !endDate) throw new Error("Faltam dados essenciais (Título, Data Início, Data Fim).");

      const startDateTime = parseDateTime(startDate, startTime);
      // Usamos 'let' para que a data de término possa ser modificada
      let endDateTime = parseDateTime(endDate, endTime);
      const eventOptions = { description: description, location: location };
      
      if (startTime && endTime) {
        calendar.createEvent(title, startDateTime, endDateTime, eventOptions);
      } else {
        endDateTime.setDate(endDateTime.getDate() + 1);
        calendar.createAllDayEvent(title, startDateTime, endDateTime, eventOptions);
      }
      
      sheet.getRange(i + 1, statusColIndex + 1).setValue(`Agendado`);
      successCount++;
      
    } catch (e) {
      const errorMessage = `Erro: ${e.message}`;
      sheet.getRange(i + 1, statusColIndex + 1).setValue(errorMessage);
      errorLogs.push({
        title: title || `Linha ${i + 1}`,
        message: e.message
      });
    }
  }

  SpreadsheetApp.flush();
  return { success: successCount, errors: errorLogs };
}

function parseDateTime(datePart, timePart) {
  if (!(datePart instanceof Date)) throw new Error("Formato de data inválido.");
  let fullDate = new Date(datePart);
  if (timePart instanceof Date) {
    fullDate.setHours(timePart.getHours());
    fullDate.setMinutes(timePart.getMinutes());
  }
  return fullDate;
}

Index.html

<!DOCTYPE html>
<html>

<head>
  <base target="_top">
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

  <style>
    :root {
      --primary-color: #3498db;
      --primary-dark: #2980b9;
      --success-color: #2ecc71;
      --danger-color: #e74c3c;
      --warning-color: #f39c12;
      --body-bg: #eef1f2;
      --card-bg: #ffffff;
      --text-color: #34495e;
      --dark-gray: #2c3e50;
      --medium-gray: #bdc3c7;
      --shadow: 0 2px 5px rgba(44, 62, 80, 0.1);
      --border-radius: 0.4rem;
    }

    body,
    html {
      margin: 0;
      padding: 0;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      background-color: var(--body-bg);
      color: var(--text-color);
    }

    .content-section {
      padding: 25px;
      max-width: 1200px;
      margin: 20px auto;
    }

    h2 {
      display: flex;
      align-items: center;
      gap: 12px;
      margin: 0 0 25px 0;
      font-size: 1.7rem;
      font-weight: 500;
      color: var(--dark-gray);
      padding-bottom: 15px;
      border-bottom: 1px solid var(--medium-gray);
    }

    .toolbar {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 20px;
      flex-wrap: wrap;
      gap: 15px;
    }

    #sync-summary {
      font-size: 0.95rem;
      color: var(--text-color);
    }

    .btn {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      padding: 0.6rem 1.2rem;
      border: none;
      border-radius: var(--border-radius);
      font-size: 1rem;
      font-weight: 500;
      cursor: pointer;
      transition: all 0.2s ease;
    }

    .btn .material-icons {
      margin-right: 8px;
      font-size: 1.3em;
    }

    .btn-primary {
      background-color: var(--primary-color);
      color: white;
    }

    .btn-primary:hover:not(:disabled) {
      background-color: var(--primary-dark);
    }

    .btn:disabled {
      background-color: var(--medium-gray);
      cursor: not-allowed;
      opacity: 0.7;
    }

    .table-container {
      background-color: var(--card-bg);
      border-radius: var(--border-radius);
      box-shadow: var(--shadow);
      overflow-x: auto;
      margin-bottom: 25px;
    }

    .data-table {
      width: 100%;
      border-collapse: collapse;
    }

    .data-table th,
    .data-table td {
      padding: 12px 15px;
      text-align: left;
      border-bottom: 1px solid var(--body-bg);
      white-space: nowrap;
    }

    .data-table th {
      background-color: #f8f9fa;
      font-weight: 600;
    }

    .data-table td:first-child {
      white-space: normal;
    }

    /* Permite que o título quebre a linha */
    .data-table tr:last-child td {
      border-bottom: none;
    }

    .data-table tr:hover {
      background-color: #f1f7fb;
    }

    .status-badge {
      padding: 4px 10px;
      border-radius: 12px;
      font-size: 0.8rem;
      font-weight: 500;
      color: white;
    }

    .status-Agendar {
      background-color: var(--warning-color);
      color: var(--dark-gray);
    }

    .status-Agendado {
      background-color: var(--success-color);
    }

    .status-Erro {
      background-color: var(--danger-color);
    }

    .log-container {
      background-color: #fff;
      border-radius: var(--border-radius);
      box-shadow: var(--shadow);
      display: none;
    }

    .log-container h3 {
      margin: 0;
      padding: 15px;
      background-color: var(--danger-color);
      color: white;
      border-top-left-radius: var(--border-radius);
      border-top-right-radius: var(--border-radius);
      font-size: 1.1rem;
    }

    .log-list {
      list-style: none;
      padding: 0;
      margin: 0;
    }

    .log-list li {
      padding: 10px 15px;
      border-bottom: 1px solid var(--body-bg);
    }

    .log-list li:last-child {
      border-bottom: none;
    }

    .log-list li strong {
      color: var(--dark-gray);
    }

    .spinner-icon {
      animation: spin 1.2s linear infinite;
    }

    @keyframes spin {
      to {
        transform: rotate(360deg);
      }
    }
  </style>
</head>

<body>
  <main>
    <section class="content-section">
      <h2><span class="material-icons">event_note</span>Tarefas e Eventos</h2>
      <div class="toolbar">
        <button id="syncButton" class="btn btn-primary">
          <span class="material-icons">sync</span>
          Enviar Pendentes para Agenda
        </button>
        <div id="sync-summary"></div>
      </div>
      <div id="tableContainer" class="table-container">
        <p style="padding: 20px; text-align: center;">Carregando agendamentos...</p>
      </div>
      <div id="logContainer" class="log-container">
        <h3><span class="material-icons" style="vertical-align: bottom; margin-right: 8px;">error_outline</span>Log de
          Erros na Sincronização</h3>
        <ul id="logList"></ul>
      </div>
    </section>
  </main>

  <script>
    document.addEventListener('DOMContentLoaded', function() {
  const syncButton = document.getElementById('syncButton');
  const tableContainer = document.getElementById('tableContainer');
  const summaryDiv = document.getElementById('sync-summary');
  const logContainer = document.getElementById('logContainer');
  const logList = document.getElementById('logList');

  const HEADERS = ["Título do Evento", "Data Início", "Hora Início", "Data Fim", "Hora Fim", "Local", "Status"];

  function renderTable(agendamentos) {
    if (!agendamentos || agendamentos.length === 0) {
      tableContainer.innerHTML = '<p style="padding: 20px; text-align: center;">Nenhum agendamento encontrado na planilha.</p>';
      return;
    }

    let tableHTML = '<table class="data-table"><thead><tr>';
    HEADERS.forEach(h => tableHTML += `<th>${h}</th>`);
    tableHTML += '</tr></thead><tbody>';

    agendamentos.forEach(row => {
      const status = row['Status'] || '';
      const statusClass = `status-${status.split(':')[0]}`; 
      
      tableHTML += `<tr>`;
      HEADERS.forEach(header => {
        if(header === 'Status') {
          tableHTML += `<td><span class="status-badge ${statusClass}">${status}</span></td>`;
        } else {
          tableHTML += `<td>${row[header] || ''}</td>`;
        }
      });
      tableHTML += `</tr>`;
    });

    tableHTML += '</tbody></table>';
    tableContainer.innerHTML = tableHTML;
  }

  function loadAndRenderTable() {
    tableContainer.innerHTML = '<p style="padding: 20px; text-align: center;">Carregando agendamentos...</p>';
    logContainer.style.display = 'none'; 
    summaryDiv.textContent = ''; 
    google.script.run
      .withSuccessHandler(renderTable)
      .withFailureHandler(err => tableContainer.innerHTML = `<p style="padding:20px; color:red;">Erro ao carregar: ${err.message}</p>`)
      .getAgendamentos();
  }

  syncButton.addEventListener('click', function() {
    const originalButtonHtml = syncButton.innerHTML;
    syncButton.disabled = true;
    syncButton.innerHTML = `<span class="material-icons spinner-icon">sync</span>Sincronizando...`;
    summaryDiv.textContent = 'Processando...';
    logContainer.style.display = 'none';

    google.script.run
      .withSuccessHandler(summary => {
        loadAndRenderTable(); 
        
        summaryDiv.innerHTML = `
          <span style="color: var(--success-color);"><strong>${summary.success}</strong> agendado(s)</span> | 
          <span style="color: var(--danger-color);"><strong>${summary.errors.length}</strong> erro(s)</span>
        `;
        
        // Exibe o container de logs se houver erros
        if(summary.errors.length > 0) {
          logList.innerHTML = summary.errors.map(err => `<li><strong>${err.title}:</strong> ${err.message}</li>`).join('');
          logContainer.style.display = 'block';
        }
        
        syncButton.disabled = false;
        syncButton.innerHTML = originalButtonHtml;
      })
      .withFailureHandler(error => {
        summaryDiv.textContent = 'Falha na comunicação.';
        alert(`Erro na sincronização: ${error.message}`);
        syncButton.disabled = false;
        syncButton.innerHTML = originalButtonHtml;
      })
      .syncEvents();
  });

  loadAndRenderTable();
});
  </script>
</body>

</html>

Acompanhe nas redes sociais

Projetos Personalizados

Precisa de uma solução sob medida?

© 2025 Transformando Planilhas. Todos os direitos reservados.