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.
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.
// ==========================================================================
// 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;
} <!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> Precisa de uma solução sob medida?
Nós usamos cookies para analisar o tráfego do site e melhorar sua experiência. Ao clicar em 'Aceitar', você concorda com o uso de ferramentas de estatística, conforme detalhado em nossa Política de Privacidade.