Código.gs
// =================================================================
// CONSTANTES GLOBAIS
// =================================================================
// Defina aqui o nome exato da aba que contém seus dados.
const NOME_DA_ABA = 'Clientes';
// =================================================================
// FUNÇÃO PRINCIPAL
// =================================================================
/**
* Função principal que é executada quando acessamos o link do nosso App.
* Ela serve a nossa página HTML.
*/
function doGet(e) {
return HtmlService.createHtmlOutputFromFile('Index')
.setTitle('Lista de Contatos')
.addMetaTag('viewport', 'width=device-width, initial-scale=1'); // Essencial para mobile
}
// =================================================================
// FUNÇÕES PÚBLICAS (Expostas ao Front-end)
// =================================================================
/**
* Busca, processa e retorna a lista de clientes da planilha.
* @returns {Array<Object>} Uma lista de objetos, onde cada objeto representa um cliente.
*/
function getClientes() {
try {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(NOME_DA_ABA);
// Pega todos os dados da planilha, exceto a primeira linha (cabeçalho).
const data = sheet.getDataRange().getValues().slice(1);
const clientes = data.map(row => {
// Ignora linhas completamente vazias
if (row.join("").trim() === "") return null;
const telefoneOriginal = row[2]; // Coluna C
// Limpa o número de telefone, removendo tudo que não for dígito.
const telefoneLimpo = String(telefoneOriginal).replace(/\D/g, '');
return {
id: row[0], // Coluna A
nome: row[1], // Coluna B
telefone: telefoneOriginal,
status: row[3], // Coluna D
// Monta o link do WhatsApp, assumindo código de país 55 (Brasil).
// Se o número já tiver 55, não adiciona novamente.
whatsappLink: `https://wa.me/${telefoneLimpo.startsWith('55') ? telefoneLimpo : '55' + telefoneLimpo}`
};
}).filter(cliente => cliente !== null && cliente.status === 'Ativo'); // Filtra nulos e clientes inativos
return clientes;
} catch (e) {
Logger.log(e);
// Lança um erro que pode ser capturado pelo .withFailureHandler no front-end
throw new Error('Não foi possível buscar os clientes. Verifique o nome da aba e a estrutura da planilha.');
}
}
Index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
/* ----- VARIÁVEIS DE CORES E ESTILOS GLOBAIS ----- */
:root {
--cor-fundo: #f0f2f5;
--cor-container: #ffffff;
--cor-texto-principal: #333;
--cor-texto-secundario: #666;
--cor-borda: #e0e0e0;
--cor-sombra: rgba(0, 0, 0, 0.08);
--cor-whatsapp: #25D366;
--cor-whatsapp-hover: #128C7E;
--cor-busca-fundo: #f8f9fa;
}
/* ----- ESTILOS BASE ----- */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background-color: var(--cor-fundo);
margin: 0;
padding: 20px;
color: var(--cor-texto-principal);
}
/* ----- CONTAINER PRINCIPAL ----- */
.container {
max-width: 700px;
margin: 0 auto;
background-color: var(--cor-container);
border-radius: 12px;
box-shadow: 0 4px 12px var(--cor-sombra);
overflow: hidden;
}
/* ----- CABEÇALHO ----- */
header {
padding: 20px;
border-bottom: 1px solid var(--cor-borda);
}
h1 {
margin: 0;
font-size: 24px;
text-align: center;
}
/* ----- LISTA DE CLIENTES ----- */
main {
padding: 10px 20px 20px 20px;
}
#client-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.client-card {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
border: 1px solid var(--cor-borda);
border-radius: 8px;
transition: background-color 0.2s ease-in-out;
}
.client-card:hover {
background-color: #f9f9f9;
}
.client-name {
font-size: 18px;
font-weight: 500;
}
/* ----- BOTÃO WHATSAPP ----- */
.whatsapp-btn {
display: inline-flex;
align-items: center;
gap: 8px;
background-color: var(--cor-whatsapp);
color: white;
text-decoration: none;
padding: 8px 15px;
border-radius: 20px;
font-weight: bold;
font-size: 14px;
transition: background-color 0.2s ease-in-out;
white-space: nowrap; /* Impede que o texto quebre linha */
}
.whatsapp-btn:hover {
background-color: var(--cor-whatsapp-hover);
}
/* SVG do Ícone do WhatsApp */
.whatsapp-btn svg {
width: 18px;
height: 18px;
fill: currentColor;
}
/* ----- MENSAGENS DE ESTADO ----- */
.status-message {
text-align: center;
padding: 40px 20px;
color: var(--cor-texto-secundario);
font-size: 16px;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>Lista de Clientes</h1>
</header>
<main>
<div id="client-list">
<!-- Cards dos clientes serão inseridos aqui pelo JavaScript -->
<p id="status-message" class="status-message">Carregando contatos...</p>
</div>
</main>
</div>
<script>
// Variável global para armazenar todos os clientes após o carregamento inicial.
let allClients = [];
// Ícone do WhatsApp em formato SVG para ser usado no botão.
const whatsappIcon = `<svg viewBox="0 0 448 512"><path d="M380.9 97.1C339 55.1 283.2 32 223.9 32c-122.4 0-222 99.6-222 222 0 39.1 10.2 77.3 29.6 111L0 480l117.7-30.9c32.4 17.7 68.9 27 106.1 27h.1c122.3 0 224.1-99.6 224.1-222 0-59.3-25.2-115-67.1-157zM223.9 439.6c-38.2 0-73.7-11.8-103.6-32.5L100 417.8l-74.4 19.6 19.9-72.6-21.6-35.2c-19.4-31.6-30.1-68.2-30.1-106.3 0-110.7 90.3-201 201-201 53.1 0 102.7 20.7 141.4 59.4s59.4 88.3 59.4 141.4-90.3 201-201 201zM362.7 311.1c-3.3-1.6-19.5-9.6-22.5-10.7-3-1.1-5.2-1.6-7.4 1.6-2.2 3.2-8.5 10.7-10.4 12.9-1.9 2.2-3.8 2.5-7.1 1s-14.4-5.3-27.5-17.1c-10.2-9.2-17.1-20.5-19.1-24.1-2-3.6-.2-5.6 1.4-7.4 1.5-1.5 3.3-3.9 5-5.2s2.2-1.6 3.2-3.8c1.1-2.2.5-4.1-.2-5.6- .8-1.6-7.4-17.7-10.1-24.4s-5.5-5.6-7.4-5.7c-1.9-.1-4.1-.1-6.3-.1s-5.2 1.6-7.9 4.2c-2.7 2.6-10.4 10.1-10.4 24.6 0 14.5 10.7 28.5 12.1 30.5 1.4 2 20.9 32 50.7 44.9 7.2 3 12.9 4.9 16.9 6.2 9.3 3.1 17.7 2.6 24.4 1.6 7.4-1.2 22.5-9.2 25.7-18.1 3.1-8.8 3.1-16.3 2.2-18.1c-.9-1.8-3.1-2.9-6.3-4.5z"/></svg>`;
/**
* Função principal que é executada quando a página carrega.
*/
document.addEventListener('DOMContentLoaded', function() {
initializeApp();
document.getElementById('search-input').addEventListener('input', handleSearch);
});
/**
* Chama a função do back-end para buscar os dados dos clientes.
*/
function initializeApp() {
google.script.run
.withSuccessHandler(onDataLoaded)
.withFailureHandler(onDataError)
.getClientes();
}
/**
* Callback executada quando os dados são recebidos com sucesso.
* @param {Array<Object>} clients - A lista de clientes vinda do back-end.
*/
function onDataLoaded(clients) {
allClients = clients;
renderClientList(allClients);
}
/**
* Callback executada em caso de erro na busca de dados.
* @param {Error} error - O objeto de erro.
*/
function onDataError(error) {
const statusMessage = document.getElementById('status-message');
statusMessage.textContent = 'Erro ao carregar os dados: ' + error.message;
}
/**
* Renderiza a lista de clientes na tela.
* @param {Array<Object>} clientsToRender - A lista de clientes a ser exibida.
*/
function renderClientList(clientsToRender) {
const listContainer = document.getElementById('client-list');
listContainer.innerHTML = ''; // Limpa a lista antes de renderizar
if (clientsToRender.length === 0) {
listContainer.innerHTML = '<p class="status-message">Nenhum cliente encontrado.</p>';
return;
}
clientsToRender.forEach(client => {
const cardHTML = `
<div class="client-card">
<span class="client-name">${client.nome}</span>
<a href="${client.whatsappLink}" target="_blank" class="whatsapp-btn">
${whatsappIcon}
<span>Conversar</span>
</a>
</div>
`;
listContainer.innerHTML += cardHTML;
});
}
</script>
</body>
</html>