// frontend/script.js document.addEventListener('DOMContentLoaded', async () => { let lastChangedField = ''; let kmTotalManual = false; let routeKm = 0; let routePedagio = 0; let paradaCount = 1; let duracaoRota = ''; let pontosUsados = ''; let retornoFlag = true; // Toggle ativo por padrão let valorKmSugestaoGlobal = 7.00; let necessidadeSegundoMotoristaKM = 1000; // Padrão let baseDiariasFreelancer = 0; // Variável para armazenar o valor base // Referências a elementos const dataInicioInput = document.querySelector('#dataInicio'); const dataFimInput = document.querySelector('#dataFim'); const origemInput = document.querySelector('#origem'); const destinoInput = document.querySelector('#destino'); const kmLivreInput = document.querySelector('#kmLivre'); const kmTotalInput = document.querySelector('#kmTotal'); const valorOrcadoInput = document.querySelector('#valorOrcado'); const valorKMInput = document.querySelector('#valorKM'); const valorDieselInput = document.querySelector('#valorDiesel'); const expectativaKM_LInput = document.querySelector('#expectativaKM_L'); const qtdDiariasMotoristasInput = document.querySelector('#qtdDiariasMotoristas'); const qtdDiariasFreelancerInput = document.querySelector('#qtdDiariasFreelancer'); const custoPercentualInput = document.querySelector('#custoPercentual'); const custoReaisDiversosInput = document.querySelector('#custoReaisDiversos'); const totalDieselInput = document.querySelector('#totalDiesel'); const totalOperacionalInput = document.querySelector('#totalOperacional'); const valorPedagiosInput = document.querySelector('#valorPedagios'); const custoTotalInput = document.querySelector('#custoTotal'); const lucroTotalInput = document.querySelector('#lucroTotal'); const percentualLucroInput = document.querySelector('#percentualLucro'); const percentualLucroInfo = document.querySelector('#percentualLucroInfo'); const salvarSimulacaoBtn = document.querySelector('#salvarSimulacaoBtn'); const setupForm = document.querySelector('#setupForm'); const resultado = document.querySelector('#resultado'); const calcularKmBtn = document.querySelector('#calcularKmBtn'); const limparBtn = document.querySelector('#limparBtn'); const valorDiariaMotoristaSetup = document.querySelector('#valorDiariaMotorista'); const valorDiariaFreelancerSetup = document.querySelector('#valorDiariaFreelancer'); const valorKmSugestaoSetup = document.querySelector('#valorKmSugestao'); const valorDieselSetup = document.querySelector('#valorDieselSetup'); const expectativaKmLSetup = document.querySelector('#expectativaKmLSetup'); const addParadaBtn = document.querySelector('#addParadaBtn'); const paradasContainer = document.querySelector('#paradasContainer'); const origemPlaceIdInput = document.querySelector('#origemPlaceId'); const destinoPlaceIdInput = document.querySelector('#destinoPlaceId'); const tipoOnibusSelect = document.querySelector('#tipoOnibus'); const calcularVoltaCheck = document.querySelector('#calcularVolta'); const duracaoViagemInput = document.querySelector('#duracaoViagem'); const loadingOverlay = document.querySelector('#loadingOverlay'); const necessidadeSegundoMotoristaInput = document.querySelector('#necessidadeSegundoMotorista'); const configuracoesBtn = document.querySelector('#configuracoesBtn'); // Referência ao botão Configurações // Funções de Formatação function formatNumber(value) { if (typeof value !== 'number') return ''; return value.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } function parseNumber(value) { if (typeof value !== 'string') return 0; // Remover "R$ ", "%" e substituir vírgulas por pontos let num = value.replace(/R\$\s?/g, '').replace(/\s?%/g, '').replace(/\./g, '').replace(',', '.'); return parseFloat(num) || 0; } function formatCurrency(value) { return `R$ ${formatNumber(value)}`; } function formatPercentage(value) { return `${formatNumber(value)} %`; } // Prevenir entrada de ponto nos campos numéricos function preventDotInput(e) { if (e.key === '.') { e.preventDefault(); } } // Formatar o valor no blur function formatOnBlur(e) { const field = e.target; const value = field.value; const parsed = parseNumber(value); if (field.name === 'custoPercentual' || field.name === 'percentualLucro') { field.value = formatPercentage(parsed); } else if (field.id === 'kmTotal' || field.id === 'kmLivre' || field.id === 'expectativaKM_L') { field.value = formatNumber(parsed); } else if (field.id === 'necessidadeSegundoMotorista') { // Apenas formatar como número puro sem "R$" e sem casas decimais field.value = Math.round(parsed).toString(); } else if (field.classList.contains('form-control-sm')) { field.value = formatCurrency(parsed); } else { field.value = formatNumber(parsed); } } // Referência aos campos que precisam de restrição const decimalFields = [ kmLivreInput, kmTotalInput, valorOrcadoInput, valorKMInput, valorDieselInput, expectativaKM_LInput, custoPercentualInput, custoReaisDiversosInput, valorDiariaMotoristaSetup, valorDiariaFreelancerSetup, valorKmSugestaoSetup, valorDieselSetup, expectativaKmLSetup, necessidadeSegundoMotoristaInput ]; decimalFields.forEach(field => { field.addEventListener('keypress', preventDotInput); field.addEventListener('blur', formatOnBlur); }); // Preencher Origem com o novo endereço já ao iniciar origemInput.value = "iBus Transportes - Garagem"; // place_id de "iBus Transportes - Garagem": "ChIJB-rHTCBQxwcRnQmFXv1W_ZI" origemPlaceIdInput.value = "ChIJB-rHTCBQxwcRnQmFXv1W_ZI"; function recalcularDiariasMotoristas() { const inicio = new Date(dataInicioInput.value); const fim = new Date(dataFimInput.value); if (!isNaN(inicio) && !isNaN(fim)) { const diffHours = Math.abs(fim - inicio)/(1000*60*60); const diarias = Math.ceil(diffHours/24); qtdDiariasMotoristasInput.value = diarias; } } dataInicioInput.addEventListener('change', ()=> { recalcularDiariasMotoristas(); atualizarCalculos(); }); dataFimInput.addEventListener('change', ()=> { recalcularDiariasMotoristas(); atualizarCalculos(); }); function atualizarCoresLucro(percentual) { percentualLucroInput.classList.remove('lucro-otimo','lucro-bom','lucro-ruim'); percentualLucroInfo.textContent = ''; if (percentual > 60) { percentualLucroInput.classList.add('lucro-otimo'); percentualLucroInfo.textContent = 'Ótimo'; } else if (percentual >= 52) { percentualLucroInput.classList.add('lucro-bom'); percentualLucroInfo.textContent = 'Bom'; } else { percentualLucroInput.classList.add('lucro-ruim'); percentualLucroInfo.textContent = 'Ruim'; } } function atualizarCalculos() { const kmTotal = parseNumber(kmTotalInput.value||0); let valorOrcado = parseNumber(valorOrcadoInput.value); let valorKM = parseNumber(valorKMInput.value||valorKmSugestaoGlobal); const valorDiesel = parseNumber(valorDieselInput.value); const expKmL = parseNumber(expectativaKM_LInput.value); const diariasMotoristas = parseNumber(qtdDiariasMotoristasInput.value||0); let diariasFreelancer = baseDiariasFreelancer; // Utilizar o valor base const cPercentual = parseNumber(custoPercentualInput.value||0); const cReais = parseNumber(custoReaisDiversosInput.value||0); // Aplicar lógica de Segundo Motorista apenas uma vez if (kmTotal > necessidadeSegundoMotoristaKM && retornoFlag && baseDiariasFreelancer === 0) { diariasFreelancer += diariasMotoristas; qtdDiariasFreelancerInput.value = diariasFreelancer; baseDiariasFreelancer = diariasFreelancer; // Atualizar o valor base para evitar incrementos futuros } else { diariasFreelancer = baseDiariasFreelancer; // Manter o valor base qtdDiariasFreelancerInput.value = diariasFreelancer; } if (kmTotal > 0) { if(lastChangedField === 'valorKM') { valorOrcado = valorKM * kmTotal; valorOrcadoInput.value = formatCurrency(valorOrcado); } else if(lastChangedField === 'valorOrcado') { valorKM = valorOrcado / kmTotal; valorKMInput.value = formatNumber(valorKM); } else { // Default: valorOrcado = valorKM * kmTotal valorOrcado = valorKM * kmTotal; valorOrcadoInput.value = formatCurrency(valorOrcado); } } const totalDiesel = (kmTotal/(expKmL||1))*(valorDiesel||0); totalDieselInput.value = isNaN(totalDiesel) ? '' : formatCurrency(totalDiesel); const vDiariaM = parseNumber(valorDiariaMotoristaSetup.value || 200); const vDiariaF = parseNumber(valorDiariaFreelancerSetup.value || 150); const totalOperacional = (diariasMotoristas*vDiariaM)+(diariasFreelancer*vDiariaF); totalOperacionalInput.value = formatCurrency(totalOperacional); valorPedagiosInput.value = routePedagio > 0 ? formatCurrency(routePedagio) : 'R$ 0,00'; const valorOrc = isNaN(valorOrcado) ? 0 : valorOrcado; const custoTotal = (valorOrc*(cPercentual/100))+(cReais||0)+totalDiesel+totalOperacional+(routePedagio||0); custoTotalInput.value = isNaN(custoTotal) ? '' : formatCurrency(custoTotal); const lucroTotal = valorOrc - custoTotal; lucroTotalInput.value = isNaN(lucroTotal) ? '' : formatCurrency(lucroTotal); const percentualLucro = (lucroTotal/(valorOrc||1))*100; percentualLucroInput.value = isNaN(percentualLucro) ? '' : formatPercentage(percentualLucro); if(!isNaN(percentualLucro)) { atualizarCoresLucro(percentualLucro); } // Resetar lastChangedField após atualização lastChangedField = ''; } async function carregarSetup() { try { const res = await fetch('/api/get-setup'); if(!res.ok) return; const data = await res.json(); valorDiariaMotoristaSetup.value = formatNumber(data.valorDiariaMotorista); valorDiariaFreelancerSetup.value = formatNumber(data.valorDiariaFreelancer); valorKmSugestaoSetup.value = formatNumber(data.valorKmSugestao); valorDieselSetup.value = formatCurrency(data.valorDiesel); expectativaKmLSetup.value = formatNumber(data.expectativaKmL); necessidadeSegundoMotoristaInput.value = Math.round(parseNumber(data.necessidadeSegundoMotorista)) || 1000; // Valor padrão se não existir valorDieselInput.value = formatCurrency(data.valorDiesel); expectativaKM_LInput.value = formatNumber(data.expectativaKmL); valorKMInput.value = formatNumber(data.valorKmSugestao); valorKmSugestaoGlobal = data.valorKmSugestao; necessidadeSegundoMotoristaKM = data.necessidadeSegundoMotorista || 1000; // Inicializar baseDiariasFreelancer com o valor atual (pode ser 0 ou pré-definido) baseDiariasFreelancer = parseNumber(qtdDiariasFreelancerInput.value) || 0; } catch (error) { console.error('Erro ao carregar setup:', error); } } // Debounce para 1s let debounceTimer = null; function debouncedAutocomplete(fn, delay=1000) { return function(...args) { clearTimeout(debounceTimer); debounceTimer = setTimeout(()=>fn.apply(this,args), delay); }; } async function fetchAutocomplete(query) { // Adicionamos o parâmetro language=pt-BR para consultas em português const response = await fetch(`/api/google/autocomplete?input=${encodeURIComponent(query)}&language=pt-BR`); return await response.json(); } function initAutocomplete(inputElement, suggestionsElement, placeIdElement) { let currentQuery = ''; inputElement.addEventListener('input', debouncedAutocomplete(async (e) => { const query = e.target.value.trim(); if (query.length < 3) { suggestionsElement.innerHTML = ''; return; } // Só consulta se o valor não for o padrão "iBus Transportes - Garagem" if(inputElement.id==='origem' && query === "iBus Transportes - Garagem") { suggestionsElement.innerHTML=''; return; } try { const data = await fetchAutocomplete(query); suggestionsElement.innerHTML = ''; if (data.predictions) { data.predictions.forEach(prediction => { const div = document.createElement('div'); div.classList.add('autocomplete-suggestion'); div.textContent = prediction.description; div.style.padding = '5px'; div.addEventListener('click', () => { inputElement.value = prediction.description; suggestionsElement.innerHTML = ''; if(placeIdElement) placeIdElement.value = prediction.place_id; inputElement.classList.remove('is-invalid'); atualizarCalculos(); }); suggestionsElement.appendChild(div); }); } else { console.error('Formato inesperado das sugestões:', data); } } catch (error) { console.error('Erro no autocomplete:', error); } })); document.addEventListener('click', (e) => { if (!inputElement.contains(e.target) && !suggestionsElement.contains(e.target)) { suggestionsElement.innerHTML = ''; } }); } function initParadaAutocomplete(paradaInput, suggestionsDiv) { paradaInput.addEventListener('input', debouncedAutocomplete(async (e) => { const query = e.target.value.trim(); if (query.length < 3) { suggestionsDiv.innerHTML = ''; return; } try { const data = await fetchAutocomplete(query); suggestionsDiv.innerHTML = ''; if (data.predictions) { data.predictions.forEach(prediction => { const div = document.createElement('div'); div.classList.add('autocomplete-suggestion'); div.textContent = prediction.description; div.style.padding = '5px'; div.addEventListener('click', () => { paradaInput.value = prediction.description; suggestionsDiv.innerHTML = ''; paradaInput.classList.remove('is-invalid'); atualizarCalculos(); }); suggestionsDiv.appendChild(div); }); } else { console.error('Formato inesperado das sugestões:', data); } } catch (error) { console.error('Erro no autocomplete da parada:', error); } })); document.addEventListener('click', (e) => { if (!paradaInput.contains(e.target) && !suggestionsDiv.contains(e.target)) { suggestionsDiv.innerHTML = ''; } }); } // Inicializar autocomplete para Origem e Destino initAutocomplete(origemInput, document.querySelector('#origem-suggestions'), origemPlaceIdInput); initAutocomplete(destinoInput, document.querySelector('#destino-suggestions'), destinoPlaceIdInput); // Campos que disparam recalculo ao mudar [valorDieselInput, expectativaKM_LInput, qtdDiariasMotoristasInput, custoPercentualInput, custoReaisDiversosInput].forEach(el=>{ el.addEventListener('input', ()=>{ atualizarCalculos(); }); }); // Adicionar event listener para o campo "QTD Diárias Freelancer" para recalcular quando alterado manualmente qtdDiariasFreelancerInput.addEventListener('input', () => { baseDiariasFreelancer = parseNumber(qtdDiariasFreelancerInput.value) || 0; atualizarCalculos(); }); // Inicializar autocomplete para paradas existentes (parada1) const paradasExistentes = paradasContainer.querySelectorAll('.parada-div'); paradasExistentes.forEach(paradaDiv => { const paradaInput = paradaDiv.querySelector('.parada'); const suggestionsDiv = paradaDiv.querySelector('.parada-suggestions'); initParadaAutocomplete(paradaInput, suggestionsDiv); }); // Evento para adicionar nova parada addParadaBtn.addEventListener('click', () => { paradaCount++; const div = document.createElement('div'); div.classList.add('mb-2','position-relative','parada-div'); const input = document.createElement('input'); input.type='text'; input.classList.add('form-control','form-control-sm','parada','autocomplete'); input.placeholder = `Parada ${paradaCount}`; input.id = `parada${paradaCount}`; input.name = `parada${paradaCount}`; const suggestionsDiv = document.createElement('div'); suggestionsDiv.classList.add('autocomplete-suggestions','parada-suggestions'); div.appendChild(input); div.appendChild(suggestionsDiv); // Adicionar botão 'Remover' apenas para paradas 2 e além const removeBtn = document.createElement('button'); removeBtn.type = 'button'; removeBtn.classList.add('btn','btn-outline-danger','btn-sm','removeParadaBtn','mt-1'); removeBtn.textContent = 'Remover'; div.appendChild(removeBtn); paradasContainer.appendChild(div); initParadaAutocomplete(input, suggestionsDiv); // Evento para remover parada removeBtn.addEventListener('click', () => { paradasContainer.removeChild(div); atualizarCalculos(); }); }); // Evento para submissão do formulário de setup setupForm.addEventListener('submit', async (e) => { e.preventDefault(); const valorDiariaMotorista = parseNumber(valorDiariaMotoristaSetup.value||200); const valorDiariaFreelancer = parseNumber(valorDiariaFreelancerSetup.value||150); const valorKmSugestao = parseNumber(valorKmSugestaoSetup.value||7.00); const valorDiesel = parseNumber(valorDieselSetup.value||6.70); const expectativaKmL = parseNumber(expectativaKmLSetup.value||2.8); const necessidadeSegundoMotorista = parseNumber(necessidadeSegundoMotoristaInput.value||1000); try { const response = await fetch('/api/setup', { // Certifique-se de que este endpoint exista no backend method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({ valorDiariaMotorista, valorDiariaFreelancer, valorKmSugestao, valorDiesel, expectativaKmL, necessidadeSegundoMotorista }) }); if(!response.ok) throw new Error('Erro na requisição de setup'); const data = await response.json(); alert('Configurações salvas com sucesso!'); await carregarSetup(); atualizarCalculos(); const modalElem = document.getElementById('setupModal'); const modal = bootstrap.Modal.getInstance(modalElem); if (modal) { modal.hide(); configuracoesBtn.focus(); // Transferir foco para o botão Configurações } } catch(error) { console.error('Erro ao salvar configurações:', error); alert('Erro ao salvar configurações. Verifique o console para mais detalhes.'); } }); // Função para buscar coordenadas pelo place_id async function getCoords(place_id){ // Adicionamos o parâmetro language=pt-BR para retornar resultados em português const r = await fetch('/api/google/details?place_id='+place_id+'&language=pt-BR'); if(!r.ok) return null; const data = await r.json(); // {lat,lng} return { lat: parseFloat(data.lat), lng: parseFloat(data.lng) }; } // Converter duracao tipo "43h45min" para "X dias Y horas Z minutos" function formatarDuracao(dur) { // dur algo tipo "43h45min" const match = dur.match(/(\d+)h(\d+)min/); if(!match) return dur; let horas = parseInt(match[1],10); let minutos = parseInt(match[2],10); let dias = Math.floor(horas/24); horas = horas % 24; let str=''; if(dias>0) str+= `${dias}d `; if(horas>0) str+= `${horas}h `; if(minutos>0) str+= `${minutos}min`; return str.trim(); } async function checkHistorico(pontosStrLocal, tipoOnibus, retornoFlag) { // Ler histórico e verificar se existe linha com mesmos pontos, mesmo veiculo, eixos, retorno const res = await fetch('/api/historico'); if(!res.ok) return null; const data = await res.json(); // array de arrays const retornoVal = retornoFlag?'true':'false'; const veiculo='onibus';// Alterado para 'onibus' for(let line of data) { // CSV: ...Pontos,Duracao,Retorno,Veiculo,Eixos // Pontos = line[20], Duracao=line[21], Retorno=line[22], Veiculo=line[23], Eixos=line[24] if(line[20]===pontosStrLocal && line[22]===retornoVal && line[23]===veiculo && line[24]===tipoOnibus) { // Encontrou histórico // Como pedágios não estão sendo salvos, forçamos a recalculação return null; } } return null; } async function calcRotasBrasil() { const tipoOnibus = tipoOnibusSelect.value; retornoFlag = calcularVoltaCheck.checked; const origemPlaceId = origemPlaceIdInput.value; const destinoPlaceId = destinoPlaceIdInput.value; if(!origemPlaceId || !destinoPlaceId) throw new Error('Origem ou Destino não selecionados corretamente.'); const origemCoords = await getCoords(origemPlaceId); const destinoCoords = await getCoords(destinoPlaceId); if(!origemCoords||!destinoCoords) throw new Error('Coordenadas inválidas.'); const paradas = Array.from(document.querySelectorAll('.parada-div .parada')).map(p=>p.value.trim()).filter(x=>x); let paradaCoords = []; for(let i=0; i0) { const place_id = autoData.predictions[0].place_id; const c = await getCoords(place_id); if(c) paradaCoords.push(c); } } let pontosStrLocal = `${origemCoords.lng.toFixed(6)},${origemCoords.lat.toFixed(6)}`; for (let pc of paradaCoords) { pontosStrLocal += `;${pc.lng.toFixed(6)},${pc.lat.toFixed(6)}`; } pontosStrLocal += `;${destinoCoords.lng.toFixed(6)},${destinoCoords.lat.toFixed(6)}`; if(retornoFlag){ // Adicionar o retorno: destino -> paradas -> origem let retornoPontos = ''; retornoPontos += `;${destinoCoords.lng.toFixed(6)},${destinoCoords.lat.toFixed(6)}`; for (let i = paradaCoords.length -1; i >=0; i--){ retornoPontos += `;${paradaCoords[i].lng.toFixed(6)},${paradaCoords[i].lat.toFixed(6)}`; } retornoPontos += `;${origemCoords.lng.toFixed(6)},${origemCoords.lat.toFixed(6)}`; pontosStrLocal += retornoPontos; } pontosUsados = pontosStrLocal; // Check historico let histData = await checkHistorico(pontosStrLocal, tipoOnibus, retornoFlag); if(histData) { // Utilize os dados do histórico (implementação futura) // Atualmente, forçamos a recalculação return; } const body = {pontos:pontosStrLocal, veiculo:'onibus', eixo:tipoOnibus, retorno: retornoFlag}; // Adicionado 'retorno' const response = await fetch('/api/calcular-km',{ method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body) }); if(!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || 'Erro ao calcular KM'); } const data = await response.json(); const rotas = data.rotas || []; const rota = rotas[0] || {}; routeKm = rota.distancia || 0; routePedagio = rota.valorPedagio || 0; duracaoRota = rota.duracao || ''; // formatar duracao duracaoRota = formatarDuracao(duracaoRota); duracaoViagemInput.value = duracaoRota || ''; } calcularKmBtn.addEventListener('click', async () => { // Mostrar o overlay de carregamento loadingOverlay.style.display = 'flex'; // Resetar mensagens de validação origemInput.classList.remove('is-invalid'); destinoInput.classList.remove('is-invalid'); let valid = true; if(!origemInput.value.trim()) { origemInput.classList.add('is-invalid'); valid = false; } if(!destinoInput.value.trim()) { destinoInput.classList.add('is-invalid'); valid = false; } if(!valid) { alert('Por favor, preencha os campos "Origem" e "Destino".'); loadingOverlay.style.display = 'none'; return; } try { await calcRotasBrasil(); const kmLivre = parseNumber(kmLivreInput.value||0); if(!kmTotalManual) { kmTotalInput.value = formatNumber(routeKm + kmLivre); } recalcularDiariasMotoristas(); atualizarCalculos(); // Atualizar o valor base de freelancer diarias baseDiariasFreelancer = parseNumber(qtdDiariasFreelancerInput.value) || 0; // Desabilitar Calcular KM, Calcular Volta, TipoOnibus, Origem, Paradas, Destino calcularKmBtn.disabled = true; calcularKmBtn.classList.remove('btn-primary'); calcularKmBtn.classList.add('btn-secondary'); calcularVoltaCheck.disabled = true; tipoOnibusSelect.disabled = true; origemInput.disabled = true; destinoInput.disabled = true; addParadaBtn.disabled = true; const allParadas = paradasContainer.querySelectorAll('.parada'); allParadas.forEach(p=>p.disabled=true); // Desabilitar o campo KM Total da Viagem kmTotalInput.disabled = true; // Desabilitar os botões "Remover" nas paradas const removeBtns = paradasContainer.querySelectorAll('.removeParadaBtn'); removeBtns.forEach(btn => btn.disabled = true); } catch(error) { console.error('Erro ao calcular KM:', error); alert('Erro ao calcular KM: ' + error.message); } finally { // Ocultar o overlay de carregamento loadingOverlay.style.display = 'none'; } }); kmTotalInput.addEventListener('input', () => { kmTotalManual = true; atualizarCalculos(); }); kmLivreInput.addEventListener('input', () => { if(!kmTotalManual) { const kmLivre = parseNumber(kmLivreInput.value||0); kmTotalInput.value = formatNumber(routeKm + kmLivre); } atualizarCalculos(); }); valorOrcadoInput.addEventListener('input', ()=>{ lastChangedField='valorOrcado'; atualizarCalculos(); }); valorKMInput.addEventListener('input', ()=>{ lastChangedField='valorKM'; atualizarCalculos(); }); salvarSimulacaoBtn.addEventListener('click', async ()=>{ const inicio = dataInicioInput.value; const fim = dataFimInput.value; const origem = origemInput.value; const destino = destinoInput.value; const paradas = Array.from(document.querySelectorAll('.parada-div .parada')).map(p=>p.value).filter(p=>p); const tipoOnibus = tipoOnibusSelect.value; const kmLivre = parseNumber(kmLivreInput.value||0); const valorOrcado = parseNumber(valorOrcadoInput.value||0); const valorDiesel = parseNumber(valorDieselInput.value||0); const expectativaKM_L = parseNumber(expectativaKM_LInput.value||0); const qtdDiariasMotoristas = parseNumber(qtdDiariasMotoristasInput.value||0); let qtdDiariasFreelancer = parseNumber(qtdDiariasFreelancerInput.value||0); const custoPercentual = parseNumber(custoPercentualInput.value||0); const custoReaisDiversos = parseNumber(custoReaisDiversosInput.value||0); const kmTotal = parseNumber(kmTotalInput.value||0); const valorKM = parseNumber(valorKMInput.value||0); try{ const response = await fetch('/api/simulador',{ method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({ dataInicio:inicio, dataFim:fim, origem, destino, paradas, kmLivre, valorOrcado, valorDiesel, expectativaKM_L, tipoOnibus, qtdDiariasMotoristas, qtdDiariasFreelancer, custoPercentual, custoReaisDiversos, kmTotalRotas:(kmTotal - kmLivre), valorKmManual:valorKM, pontosStr: pontosUsados, duracao: duracaoRota, retorno: retornoFlag }) }); if(!response.ok) throw new Error('Erro ao salvar simulação'); const data = await response.json(); resultado.textContent = JSON.stringify(data,null,2); } catch(error) { console.error(error); alert('Erro ao salvar simulação.'); } }); limparBtn.addEventListener('click', ()=>{ origemInput.value = "iBus Transportes - Garagem"; origemPlaceIdInput.value = "ChIJB-rHTCBQxwcRnQmFXv1W_ZI"; destinoInput.value = ''; destinoPlaceIdInput.value = ''; kmLivreInput.value = ''; kmTotalInput.value = ''; valorOrcadoInput.value = ''; valorKMInput.value = formatNumber(valorKmSugestaoGlobal); qtdDiariasMotoristasInput.value = ''; qtdDiariasFreelancerInput.value = ''; custoPercentualInput.value = ''; custoReaisDiversosInput.value = ''; totalDieselInput.value = ''; totalOperacionalInput.value = ''; valorPedagiosInput.value = ''; custoTotalInput.value = ''; lucroTotalInput.value = ''; percentualLucroInput.value = ''; percentualLucroInfo.textContent = ''; percentualLucroInput.classList.remove('lucro-otimo','lucro-bom','lucro-ruim'); routeKm=0; routePedagio=0; kmTotalManual=false; lastChangedField=''; duracaoRota=''; duracaoViagemInput.value=''; tipoOnibusSelect.value='2'; calcularVoltaCheck.checked = true; // Mantenha o toggle ativado retornoFlag = true; // Atualize a flag calcularKmBtn.disabled = false; calcularKmBtn.classList.remove('btn-secondary'); calcularKmBtn.classList.add('btn-primary'); calcularVoltaCheck.disabled = false; tipoOnibusSelect.disabled = false; origemInput.disabled = false; destinoInput.disabled = false; addParadaBtn.disabled = false; kmTotalInput.disabled = false; // Reativar o campo // Reabilitar os botões "Remover" nas paradas const removeBtns = paradasContainer.querySelectorAll('.removeParadaBtn'); removeBtns.forEach(btn => { btn.disabled = false; btn.style.display = 'inline-block'; }); const paradas = paradasContainer.querySelectorAll('.parada-div'); let count=0; paradas.forEach(div=>{ count++; if(count>1) div.remove(); else { const inp = div.querySelector('.parada'); inp.value=''; inp.id='parada1'; inp.name='parada1'; inp.disabled=false; } }); paradaCount=1; // Resetar baseDiariasFreelancer baseDiariasFreelancer = 0; qtdDiariasFreelancerInput.value = 0; resultado.textContent = ''; dataInicioInput.value=''; dataFimInput.value=''; atualizarCalculos(); }); // Inicializar await carregarSetup(); atualizarCalculos(); const paradaInicial = paradasContainer.querySelector('.parada-div'); if(paradaInicial) { const input = paradaInicial.querySelector('.parada'); const suggestionsDiv = paradaInicial.querySelector('.parada-suggestions'); initParadaAutocomplete(input, suggestionsDiv); } // Gerenciar foco após fechar o modal para evitar erro de aria-hidden const setupModal = document.getElementById('setupModal'); setupModal.addEventListener('hidden.bs.modal', () => { configuracoesBtn.focus(); }); });