INTRODUÇÃO
Post anterior: Dashboard de Produtividade Pessoal T2E02: Caso de Teste 1 Tarefa sem pausa
O objetivo deste post é apresentar o relato de um bug encontrado durante a execução do "Caso de Teste 1: Tarefa sem pausa", na temporada 2 da série Dashboard de Produtividade Pessoal.
Promp para Gemini 2.5 Pro (Prévia)
- Frontend Dashboard de Produtividade Pessoal
- Backend Google Sheet
- Apps Scritp para integração do Dashboard com Google Sheet
- CódigoTarefa: S00.1.1.01; NomeTarefa: Tarefa 1 Teste 1; TamanhoTarefa: 5
- CódigoTarefa: S00.1.1.02; NomeTarefa: Tarefa 2 Teste 1; TamanhoTarefa: 1
- CódigoTarefa: S00.1.1.03; NomeTarefa: Tarefa 3 Teste 1; TamanhoTarefa: 3
- Ao clicar no botão Iniciar Jornada, não foi registrada a hora e nem iniciada a contagem do tempo.
- Ao clicar no botão "Play" para iniciar a tarefa registrada, o botão não estava habilitado.
- Ao clicar no botão para finalizar a tarefa, o botão "Finalizar" estava habilitado, mas não funcionou.
Resposta do Gemini 2.5 Pro (Prévia)
Olá! Perfeito. Agradeço pelo relatório detalhado dos bugs. Casos de teste são essenciais para encontrar problemas, e sua descrição foi muito clara.
Analisei os três bugs e a boa notícia é que todos eles têm a mesma causa raiz, o que torna a correção bem direta.
Diagnóstico da Causa Raiz
A causa principal dos três problemas está na função de inicialização do nosso script (init()
) dentro do arquivo dashboard.js
. Após a mudança de arquitetura e a movimentação do código, esquecemos de "conectar" os eventos de clique de alguns botões principais (como "Iniciar Jornada") às suas respectivas funções JavaScript.
Isso gerou um efeito dominó:
- Bug 1 (Jornada não inicia): O botão "Iniciar Jornada" não tinha seu
addEventListener
registrado. Por isso, ao clicar, nada acontecia. - Bug 2 (Botão "Play" desabilitado): O botão para iniciar uma tarefa só é habilitado após o início da jornada (
state.workday.startTime
precisa ter um valor). Como a jornada nunca iniciava, os botões de tarefa permaneciam desabilitados. - Bug 3 (Finalizar não funciona): O fluxo para testar o botão "Finalizar" (
btnStopTask
) estava quebrado porque não era possível iniciar uma tarefa corretamente. Além disso, o listener do botão de pausa/retomar também estava faltando, o que prejudicaria a interação.
Solução
A solução é garantir que todos os botões da interface tenham seus addEventListener
corretamente registrados na função init()
do dashboard.js
.
Abaixo, forneço a versão corrigida e completa do arquivo dashboard.js
. Ele contém a lógica de inicialização correta, que resolve os três bugs de uma só vez.
Código Corrigido para dashboard.js
Copie todo o código abaixo e substitua completamente o conteúdo do seu arquivo dashboard.js
atual. As correções estão destacadas com comentários.
document.addEventListener('DOMContentLoaded', function() {
// ---- VERIFICAÇÃO DE LOGIN E AUTENTICAÇÃO ----
// Esta parte permanece a mesma, garantindo que o usuário esteja logado.
const nomeUsuario = localStorage.getItem('usuarioNome');
const emailUsuario = localStorage.getItem('usuarioEmail');
if (!nomeUsuario || !emailUsuario) {
alert("Acesso negado. Por favor, faça o login.");
window.location.href = 'index.html';
return;
}
document.getElementById('welcome-message').textContent = `Logado como: ${nomeUsuario}`;
document.getElementById('logoutButton').addEventListener('click', () => {
if (typeof google !== 'undefined' && google.accounts && google.accounts.id) {
google.accounts.id.disableAutoSelect();
}
localStorage.clear();
window.location.href = 'index.html';
});
// ---- LÓGICA DO DASHBOARD ----
// --- ELEMENTOS DO DOM ---
const loader = document.getElementById('loader');
const dashboardContent = document.getElementById('dashboard-content');
const formAddTask = document.getElementById('formAddTask');
const taskListEl = document.getElementById('taskList');
const btnStartWorkday = document.getElementById('btnStartWorkday');
const btnEndWorkday = document.getElementById('btnEndWorkday');
const workdayStartTimeEl = document.getElementById('workdayStartTime');
const workdayDurationEl = document.getElementById('workdayDuration');
const productivityRateEl = document.getElementById('productivityRate');
const totalPointsCompletedEl = document.getElementById('totalPointsCompleted');
const currentTaskNameEl = document.getElementById('currentTaskName');
const currentTaskTimerEl = document.getElementById('currentTaskTimer');
const btnPauseResumeTask = document.getElementById('btnPauseResumeTask');
const btnStopTask = document.getElementById('btnStopTask');
// --- ESTADO DA APLICAÇÃO ---
let state = {
tasks: [],
workday: { startTime: null, endTime: null, durationTimer: null, totalSeconds: 0, totalEffectiveSeconds: 0 },
currentTask: { id: null, timer: null, isPaused: false, effectiveSeconds: 0 },
};
// --- FUNÇÕES DE UTILIDADE ---
const formatTime = (totalSeconds) => {
if (isNaN(totalSeconds) || totalSeconds < 0) return "00:00:00";
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = Math.floor(totalSeconds % 60);
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
};
const formatTimeHours = (totalSeconds) => {
if (isNaN(totalSeconds) || totalSeconds <= 0) return 0;
return totalSeconds / 3600;
};
// --- RENDERIZAÇÃO E UI ---
const renderTasks = () => {
if (!taskListEl) return;
if (state.tasks.length === 0) {
taskListEl.innerHTML = '<p class="text-gray-500 text-center py-4">Nenhuma tarefa adicionada ainda.</p>';
return;
}
taskListEl.innerHTML = '';
state.tasks.forEach(task => {
const taskEl = document.createElement('div');
taskEl.className = `p-4 border rounded-lg flex items-center justify-between transition-all duration-300 ${task.status === 'completed' ? 'bg-green-50 border-green-200' : 'bg-white'} ${state.currentTask.id === task.id ? 'task-active' : ''}`;
taskEl.id = `task-${task.id}`;
let statusIndicator = '';
if (task.status === 'completed') {
statusIndicator = '<span class="text-xs font-medium bg-green-100 text-green-800 py-1 px-2 rounded-full">Concluída</span>';
} else if (task.status === 'in-progress') {
statusIndicator = '<span class="text-xs font-medium bg-blue-100 text-blue-800 py-1 px-2 rounded-full">Em Progresso</span>';
}
taskEl.innerHTML = `
<div>
<p class="font-semibold">${task.name} <span class="text-sm font-normal text-gray-500">(${task.code || 'N/C'})</span></p>
<p class="text-sm text-gray-600">Pontos: ${task.points} | Duração Efetiva: ${formatTime(task.effectiveDuration)}</p>
</div>
<div class="flex items-center space-x-2">
${statusIndicator}
<button data-task-id="${task.id}" class="btn-start-task bg-blue-500 hover:bg-blue-600 text-white p-2 rounded-full disabled:bg-gray-300 disabled:cursor-not-allowed" ${task.status !== 'pending' || !state.workday.startTime || !!state.currentTask.id ? 'disabled' : ''} title="Iniciar Tarefa">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
</button>
</div>
`;
taskListEl.appendChild(taskEl);
});
document.querySelectorAll('.btn-start-task').forEach(btn => {
btn.addEventListener('click', () => handleStartTask(btn.dataset.taskId));
});
};
const updateUI = () => {
renderTasks();
btnStartWorkday.disabled = !!state.workday.startTime;
btnEndWorkday.disabled = !state.workday.startTime || !!state.workday.endTime || !!state.currentTask.id;
const hasActiveTask = !!state.currentTask.id;
btnPauseResumeTask.disabled = !hasActiveTask;
btnStopTask.disabled = !hasActiveTask;
btnPauseResumeTask.textContent = state.currentTask.isPaused ? 'Retomar' : 'Pausar';
btnPauseResumeTask.className = `btn font-bold py-2 px-4 rounded-lg ${state.currentTask.isPaused ? 'bg-green-500 hover:bg-green-600' : 'bg-yellow-500 hover:bg-yellow-600'} text-white`;
const totalCompletedPoints = state.tasks.filter(t => t.status === 'completed').reduce((sum, t) => sum + parseInt(t.points, 10), 0);
totalPointsCompletedEl.textContent = totalCompletedPoints;
const hoursWorked = formatTimeHours(state.workday.totalEffectiveSeconds);
const productivity = hoursWorked > 0 ? (totalCompletedPoints / hoursWorked).toFixed(2) : '0.00';
productivityRateEl.textContent = productivity;
};
// --- LÓGICA DE NEGÓCIO ---
function handleStartWorkday() {
state.workday.startTime = new Date();
workdayStartTimeEl.textContent = state.workday.startTime.toLocaleTimeString();
state.workday.durationTimer = setInterval(() => {
state.workday.totalSeconds = Math.floor((new Date() - state.workday.startTime) / 1000);
workdayDurationEl.textContent = formatTime(state.workday.totalSeconds);
if (state.currentTask.id && !state.currentTask.isPaused) {
state.workday.totalEffectiveSeconds++;
updateUI();
}
}, 1000);
updateUI();
}
function handleEndWorkday() {
// Lógica para finalizar a jornada
if(state.currentTask.id) {
alert('Por favor, finalize a tarefa atual antes de encerrar a jornada.');
return;
}
clearInterval(state.workday.durationTimer);
state.workday.endTime = new Date();
updateUI();
// Aqui você chamaria google.script.run para salvar os dados da jornada, se necessário
}
function handleAddTask(e) {
e.preventDefault();
const form = e.target;
const newTask = {
name: form.taskName.value,
code: form.taskCode.value,
points: parseInt(form.taskPoints.value, 10),
status: 'pending',
effectiveDuration: 0
};
form.querySelector('button[type="submit"]').disabled = true;
google.script.run
.withSuccessHandler(taskAdicionadaComSucesso)
.withFailureHandler(falhaAoSalvar)
.addTask(newTask);
}
function taskAdicionadaComSucesso(taskComId) {
formAddTask.reset();
formAddTask.querySelector('button[type="submit"]').disabled = false;
state.tasks.push(taskComId);
updateUI();
}
function falhaAoSalvar(error) {
alert('Erro ao salvar a tarefa: ' + error.message);
formAddTask.querySelector('button[type="submit"]').disabled = false;
}
function handleStartTask(taskId) {
if (state.currentTask.id) {
alert('Já existe uma tarefa em andamento.');
return;
}
const task = state.tasks.find(t => t.id === taskId);
if (task) {
task.status = 'in-progress';
state.currentTask = { id: taskId, timer: null, isPaused: false, effectiveSeconds: parseInt(task.effectiveDuration, 10) || 0 };
currentTaskNameEl.textContent = task.name;
currentTaskTimerEl.textContent = formatTime(state.currentTask.effectiveSeconds);
state.currentTask.timer = setInterval(() => {
if (!state.currentTask.isPaused) {
state.currentTask.effectiveSeconds++;
currentTaskTimerEl.textContent = formatTime(state.currentTask.effectiveSeconds);
}
}, 1000);
updateUI();
}
}
function handlePauseResumeTask() {
state.currentTask.isPaused = !state.currentTask.isPaused;
updateUI();
}
function handleStopTask() {
clearInterval(state.currentTask.timer);
const taskId = state.currentTask.id;
const task = state.tasks.find(t => t.id === taskId);
if (task) {
task.status = 'completed';
task.effectiveDuration = state.currentTask.effectiveSeconds;
google.script.run
.withSuccessHandler(() => console.log(`Tarefa ${taskId} atualizada para 'completed' na planilha.`))
.withFailureHandler((err) => alert(`Erro ao atualizar tarefa: ${err.message}`))
.updateTaskStatus(taskId, 'completed', task.effectiveDuration);
}
state.currentTask = { id: null, timer: null, isPaused: false, effectiveSeconds: 0 };
currentTaskNameEl.textContent = 'Nenhuma tarefa iniciada';
currentTaskTimerEl.textContent = '00:00:00';
updateUI();
}
// --- INICIALIZAÇÃO DA APLICAÇÃO ---
function init() {
// CORREÇÃO: Adicionando os listeners que estavam faltando
formAddTask.addEventListener('submit', handleAddTask);
btnStartWorkday.addEventListener('click', handleStartWorkday);
btnEndWorkday.addEventListener('click', handleEndWorkday);
btnPauseResumeTask.addEventListener('click', handlePauseResumeTask);
btnStopTask.addEventListener('click', handleStopTask);
// Busca os dados iniciais da planilha
google.script.run
.withSuccessHandler(dadosCarregadosComSucesso)
.withFailureHandler(falhaAoCarregar)
.getInitialData();
}
function dadosCarregadosComSucesso(data) {
console.log("Dados recebidos da planilha:", data);
state.tasks = data.tasks || [];
loader.style.display = 'none';
dashboardContent.classList.remove('hidden');
updateUI();
}
function falhaAoCarregar(error) {
loader.style.display = 'none';
alert('Não foi possível carregar os dados da planilha: ' + error.message);
}
init();
});
Próximos Passos
- Substitua o código: Vá ao editor do Apps Script e substitua todo o conteúdo do seu
dashboard.js
pelo código corrigido acima. - Salve o Projeto: Clique no ícone de disquete ("Salvar projeto").
- Teste Novamente: Acesse a URL da sua implantação do App da Web e execute o Caso de Teste 1 novamente.
Agora, os botões de jornada e de tarefas devem funcionar conforme o esperado. Por favor, realize os testes e me informe o resultado!
Pergunta ao Gemini
Resposta do Gemini
Olá! Ótima pergunta. Essa é uma etapa crucial e um ponto que confunde muitos iniciantes no Apps Script.
Salvar as alterações no Apps Script envolve duas fases: 1) Salvar o código no editor e 2) Publicar as alterações para que elas apareçam no seu aplicativo web.
Aqui está o passo a passo detalhado:
Fase 1: Salvar o Código no Editor
Esta é a parte mais simples. Você tem duas maneiras principais de fazer isso:
-
Usando o Ícone de Salvar:
- No topo do editor do Apps Script, procure pelo ícone de um disquete 💾.
- Clique neste ícone (
Salvar projeto
) para salvar todas as alterações que você fez nos seus arquivos (Code.gs
,Dashboard.html
, etc.).
-
Usando o Atalho de Teclado:
- Como na maioria dos editores, você pode usar o atalho de teclado:
Ctrl + S
(no Windows ou Linux)Cmd + S
(no Mac)
- Como na maioria dos editores, você pode usar o atalho de teclado:
Dica: Você saberá que um arquivo tem alterações não salvas se aparecer um pequeno asterisco *
ao lado do nome do arquivo na aba. Depois de salvar, o asterisco desaparece.
Fase 2: Publicar (Implantar) as Alterações no Aplicativo Web
Esta é a parte mais importante e que muitas vezes é esquecida. Apenas salvar o código não atualiza a URL do seu aplicativo web. Você precisa criar uma nova versão da sua implantação.
-
Abra o Gerenciador de Implantações:
- No topo da tela, clique no botão azul
Implantar
. - No menu que aparecer, selecione
Gerenciar implantações
.
- No topo da tela, clique no botão azul
-
Edite sua Implantação Ativa:
- Você verá sua implantação do "App da Web" na lista. Ela estará marcada como "Ativa".
- À direita dela, clique no ícone de lápis (✏️) para editar.
-
Crie uma Nova Versão:
- Na caixa de diálogo que se abre, o mais importante é o seletor
Versão
. - Clique nele e escolha
Nova versão
.
- Na caixa de diálogo que se abre, o mais importante é o seletor
-
Implante a Nova Versão:
- Clique no botão azul
Implantar
na parte inferior da caixa.
- Clique no botão azul
Pronto! Suas alterações agora estão ativas na URL do seu aplicativo web. Você pode recarregar a página do seu dashboard para ver os bugs corrigidos.
Resumo Rápido
Salvar (💾) + Implantar (⚙️ Nova versão) = Mudanças no ar!
Sempre que fizer uma alteração no código que queira ver refletida no seu site, você precisa seguir esses dois passos.
Nenhum comentário:
Postar um comentário