Google Apps Script
Exception: Malformed HTML content: <!DOCTYPE html> <html> <head> <base target="_top"> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Sistema de Pagamento de Lenha</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, rgba(26, 26, 26, 0.75) 0%, rgba(45, 36, 22, 0.75) 100%), url('https://png.pngtree.com/background/20250119/original/pngtree-rustic-firewood-pile-on-wooden-background-stacked-logs-in-brown-wallpaper-picture-image_13515525.jpg') center/cover fixed no-repeat; color: #e8e6e3; padding-top: 140px; min-height: 100vh; } .header { position: fixed; top: 0; left: 0; right: 0; background: linear-gradient(135deg, #2d5016 0%, #4a7c2a 100%); color: white; padding: 20px; box-shadow: 0 2px 10px rgba(0,0,0,0.3); z-index: 1000; } .header-content { max-width: 1400px; margin: 0 auto; display: flex; align-items: center; justify-content: space-between; } .logo { height: 50px; } .header h1 { font-size: 24px; font-weight: 600; text-align: right; } .tabs { background: rgba(61, 47, 31, 0.6); max-width: 1400px; margin: 20px auto; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.3); overflow: hidden; backdrop-filter: blur(10px); } .tab-buttons { position: fixed; top: 90px; left: 0; right: 0; display: flex; background: rgba(109, 76, 65, 0.85); border-bottom: 2px solid #6d4c41; z-index: 999; max-width: 1400px; margin: 0 auto; backdrop-filter: blur(5px); } .tab-button { flex: 1; padding: 15px 20px; background: none; border: none; cursor: pointer; font-size: 16px; font-weight: 500; color: #fff; transition: all 0.3s; position: relative; } .tab-button:hover { background: rgba(141, 110, 99, 0.5); } .tab-button.active { background: rgba(93, 64, 55, 0.7); } .tab-button.active::after { content: ''; position: absolute; bottom: -2px; left: 0; right: 0; height: 3px; background: #4a7c2a; } .tab-content { display: none; padding: 30px; animation: fadeIn 0.3s; background: rgba(45, 36, 22, 0.5); border-radius: 0 0 10px 10px; backdrop-filter: blur(8px); } .tab-content.active { display: block; } @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 25px; } .section-header h2 { color: #4a7c2a; } .form-group { margin-bottom: 20px; } .form-group label { display: block; margin-bottom: 8px; color: #4a7c2a; font-weight: 500; } .form-group input, .form-group select, .form-group textarea { width: 100%; padding: 12px; border: 2px solid rgba(109, 76, 65, 0.6); border-radius: 8px; font-size: 14px; transition: all 0.3s; background: rgba(26, 20, 16, 0.6); color: #e8e6e3; backdrop-filter: blur(5px); } .form-group input:focus, .form-group select:focus, .form-group textarea:focus { outline: none; border-color: #4a7c2a; box-shadow: 0 0 0 3px rgba(74, 124, 42, 0.2); background: rgba(26, 20, 16, 0.8); } .form-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; } .radio-group { display: flex; flex-direction: column; gap: 10px; padding: 12px; background: rgba(26, 20, 16, 0.6); border: 2px solid rgba(109, 76, 65, 0.6); border-radius: 8px; backdrop-filter: blur(5px); } .radio-option { display: flex; align-items: center; gap: 8px; cursor: pointer; } .radio-option input[type="radio"] { width: auto; cursor: pointer; } .radio-option label { margin-bottom: 0; cursor: pointer; color: #e8e6e3; font-weight: normal; } .btn { padding: 12px 30px; border: none; border-radius: 8px; font-size: 16px; font-weight: 500; cursor: pointer; transition: all 0.3s; margin-right: 10px; } .btn:disabled { opacity: 0.5; cursor: not-allowed; } .btn-primary { background: linear-gradient(135deg, #2d5016 0%, #4a7c2a 100%); color: white; } .btn-primary:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(45, 80, 22, 0.3); } .btn-secondary { background: rgba(109, 76, 65, 0.9); color: white; backdrop-filter: blur(5px); } .btn-secondary:hover { background: rgba(93, 64, 55, 0.9); transform: translateY(-2px); box-shadow: 0 5px 15px rgba(93, 64, 55, 0.3); } .btn-outline { background: rgba(61, 47, 31, 0.6); color: #4a7c2a; border: 2px solid #4a7c2a; backdrop-filter: blur(5px); } .btn-outline:hover { background: #4a7c2a; color: white; } .button-group { margin-top: 30px; display: flex; gap: 10px; } .table-container { overflow-x: auto; margin-top: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.3); background: rgba(26, 20, 16, 0.7); max-height: 600px; overflow-y: auto; position: relative; backdrop-filter: blur(10px); } table { width: 100%; border-collapse: collapse; background: transparent; } thead { background: linear-gradient(135deg, #2d5016 0%, #4a7c2a 100%); color: white; position: sticky; top: 0; z-index: 10; border-radius: 8px 8px 0 0; /* Arredondar superior esquerdo e direito */ } th:first-child { border-top-left-radius: 8px; } th:last-child { border-top-right-radius: 8px; } th { padding: 10px 8px; text-align: center; vertical-align: middle; border-bottom: 1px solid rgba(109, 76, 65, 0.6); color: #e8e6e3; cursor: pointer; user-select: none; position: relative; white-space: nowrap; font-size: 11px; } th:hover { background: rgba(255, 255, 255, 0.1); } th.sorted-asc::after { content: ' ▲'; opacity: 1; font-size: 10px; } th.sorted-desc::after { content: ' ▼'; opacity: 1; font-size: 10px; } th.group-border-left { border-left: 3px solid rgba(255, 255, 255, 0.3); } td { padding: 3px 5px; /* altura menor das linhas */ text-align: center; border-bottom: 1px solid rgba(109, 76, 65, 0.3); color: #e8e6e3; font-size: 12px; /* texto um pouco menor */ line-height: 1; /* menor espaçamento interno */ } td:first-child { text-align: left; } /* Ocultar coluna Código */ th[data-coluna="codigo"], td:nth-child(9) { display: none; } tbody tr { background: rgba(26, 20, 16, 0.4); cursor: pointer; transition: background 0.2s; } tbody tr:hover { background: rgba(45, 36, 22, 0.6); } tbody tr.selected { background: rgba(74, 124, 42, 0.3) !important; border-left: 4px solid #4a7c2a; } .status-badge { padding: 5px 12px; border-radius: 20px; font-weight: 500; font-size: 12px; display: inline-block; } .status-pago { background: #2d5016; color: #a5d6a7; } .status-aberto { background: #5d2c2c; color: #ffcdd2; } .status-emitir-nf { background: #fff3cd; color: #856404; } .status-a-pagar { background: #d1ecf1; color: #0c5460; } .btn-pagar, .btn-nota-fiscal { background: transparent; color: #e8e6e3; padding: 6px; border: none; cursor: pointer; font-size: 18px; margin-right: 5px; display: inline-flex; align-items: center; justify-content: center; transition: transform 0.2s; } .btn-imprimir { background: transparent; color: #e8e6e3; padding: 6px; border: none; cursor: pointer; font-size: 18px; margin-right: 5px; display: inline-flex; align-items: center; justify-content: center; transition: transform 0.2s; } .btn-pagar:hover, .btn-imprimir:hover, .btn-nota-fiscal:hover { transform: scale(1.2); } .btn-pagar:disabled, .btn-imprimir:disabled, .btn-nota-fiscal:disabled { opacity: 0.5; cursor: not-allowed; } .btn-excluir { background: transparent; color: #e8e6e3; padding: 6px; border: none; cursor: pointer; font-size: 18px; margin-right: 5px; display: inline-flex; align-items: center; justify-content: center; transition: transform 0.2s; } .btn-excluir:hover { transform: scale(1.2); } .btn-excluir:disabled { opacity: 0.5; cursor: not-allowed; } .btn-imprimir-tabela { background: linear-gradient(135deg, #2d5016 0%, #4a7c2a 100%); color: white; padding: 12px 30px; border: none; border-radius: 8px; font-size: 16px; font-weight: 500; cursor: pointer; transition: all 0.3s; display: inline-flex; align-items: center; gap: 8px; margin-left: 10px; } .btn-imprimir-tabela:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(45, 80, 22, 0.3); } .resultados-por-pagina { display: flex; align-items: center; gap: 8px; margin-left: auto; } .resultados-por-pagina label { color: #4a7c2a; font-weight: 500; font-size: 14px; white-space: nowrap; } .resultados-por-pagina select { width: 80px; padding: 8px; border: 2px solid rgba(109, 76, 65, 0.6); border-radius: 8px; font-size: 14px; background: rgba(26, 20, 16, 0.6); color: #e8e6e3; cursor: pointer; backdrop-filter: blur(5px); } .modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 1000; justify-content: center; align-items: center; } .modal.active { display: flex; } .modal-content { background: rgba(45, 36, 22, 0.95); padding: 30px; border-radius: 10px; max-width: 500px; width: 90%; max-height: 90vh; overflow-y: auto; box-shadow: 0 10px 40px rgba(0,0,0,0.5); border: 2px solid rgba(109, 76, 65, 0.8); backdrop-filter: blur(15px); } .modal-header { margin-bottom: 20px; padding-bottom: 15px; border-bottom: 2px solid rgba(109, 76, 65, 0.6); } .modal-header h2 { color: #4a7c2a; } .close-modal { float: right; font-size: 28px; font-weight: bold; color: #999; cursor: pointer; } .close-modal:hover { color: #e8e6e3; } .filter-section { background: rgba(61, 47, 31, 0.6); padding: 20px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 10px rgba(0,0,0,0.3); backdrop-filter: blur(8px); } .dashboard-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 30px; } .card { background: rgba(61, 47, 31, 0.65); padding: 25px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.3); border-left: 4px solid #4a7c2a; backdrop-filter: blur(8px); } .card h3 { color: #a5a19e; font-size: 14px; font-weight: 500; margin-bottom: 10px; } .card .value { color: #4a7c2a; font-size: 28px; font-weight: bold; } .chart-container { background: rgba(61, 47, 31, 0.65); padding: 25px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.3); margin-bottom: 20px; backdrop-filter: blur(8px); } .chart-container h3 { color: #4a7c2a; margin-bottom: 20px; font-size: 18px; } canvas { max-height: 300px; } .success-message { background: rgba(45, 80, 22, 0.9); color: #a5d6a7; padding: 15px; border-radius: 8px; margin-bottom: 20px; display: none; border: 1px solid #4a7c2a; backdrop-filter: blur(5px); } .error-message { background: rgba(93, 44, 44, 0.9); color: #ffcdd2; padding: 15px; border-radius: 8px; margin-bottom: 20px; display: none; border: 1px solid #c62828; backdrop-filter: blur(5px); } .linha-totais { font-weight: bold; background: #6d4c41 !important; border-top: 3px solid #6d4c41 !important; height: 40px; /* Altura aumentada */ } .linha-totais td { font-weight: bold; color: white !important; background: #6d4c41 !important; border-top: 3px solid #6d4c41 !important; padding: 10px 5px; /* Padding maior para aumentar altura */ vertical-align: middle; } .linha-totais:hover { background: #6d4c41 !important; } .linha-totais:hover td { background: #6d4c41 !important; } .loading { text-align: center; padding: 20px; color: #a5a19e; } td.group-border-left { border-left: 1px solid #6d4c41 !important; } @media print { body * { visibility: hidden; } .print-area, .print-area * { visibility: visible; } .print-area { position: absolute; left: 0; top: 0; width: 100%; } .print-area-financeiro, .print-area-financeiro * { visibility: visible; } .print-area-financeiro { position: absolute; left: 0; top: 0; width: 100%; background: white; padding: 20px; } .print-header-financeiro { display: flex; align-items: center; justify-content: space-between; margin-bottom: 30px; padding-bottom: 20px; border-bottom: 3px solid #2d5016; } .print-logo-financeiro { height: 60px; } .print-title-financeiro { font-size: 24px; font-weight: bold; color: #2d5016; text-align: center; flex-grow: 1; } .print-info-financeiro { margin-bottom: 20px; font-size: 12px; color: #333; } .print-info-financeiro strong { color: #2d5016; } .print-table-financeiro { width: 100%; border-collapse: collapse; font-size: 9px; margin-top: 10px; } .print-table-financeiro th { background: #2d5016 !important; color: white !important; padding: 8px 4px; text-align: center; border: 1px solid #2d5016; font-weight: bold; font-size: 9px; } .print-table-financeiro td { padding: 4px 3px; border: 1px solid #ccc; text-align: center; color: #000 !important; background: white !important; font-size: 8px; line-height: 1.1; } .print-table-financeiro tbody tr:nth-child(even) { background: #f5f5f5 !important; } .print-status-badge { padding: 3px 8px; border-radius: 12px; font-weight: bold; font-size: 8px; display: inline-block; } .print-status-pago { background: #c8e6c9 !important; color: #2e7d32 !important; border: 1px solid #2e7d32; } .print-status-aberto { background: #ffcdd2 !important; color: #c62828 !important; border: 1px solid #c62828; } .print-footer-financeiro { margin-top: 20px; padding-top: 10px; border-top: 2px solid #2d5016; font-size: 10px; color: #666; text-align: center; } .print-totais { margin-top: 15px; display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; font-size: 11px; } .print-total-card { background: #f5f5f5 !important; padding: 10px; border-radius: 5px; border-left: 4px solid #2d5016; } .print-total-card strong { display: block; color: #2d5016; margin-bottom: 5px; } .print-total-card .valor { font-size: 14px; font-weight: bold; color: #333; } /* Ajustes para comprovantes de lançamento */ .voucher { border: 2px solid #2d5016; padding: 15px; /* Reduzido de 20px */ margin-bottom: 8px; /* Reduzido de 10px */ page-break-inside: avoid; } .voucher-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; /* Reduzido de 20px */ border-bottom: 2px solid #2d5016; padding-bottom: 10px; /* Reduzido de 15px */ } .voucher-info { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; /* Reduzido de 10px */ margin-bottom: 10px; /* Reduzido de 15px */ font-size: 13px; } .voucher-field { margin-bottom: 5px; /* Reduzido de 8px */ } .voucher-signature { margin-top: 30px; /* Reduzido de 50px */ display: flex; justify-content: space-between; } .voucher-separator { border-top: 2px dashed #999; margin: 12px 0; /* Reduzido de 20px */ text-align: center; position: relative; } @page { size: landscape; margin: 10mm; } } /* ==================== ONBOARDING STYLES ==================== */ #onboardingOverlay { display: none; position: fixed; inset: 0; background: linear-gradient(135deg, #0f0f1a 0%, #1a1a2e 50%, #16213e 100%); z-index: 9999; overflow-y: auto; align-items: center; justify-content: center; } #onboardingOverlay.active { display: flex; } .onb-container { max-width: 560px; width: 95%; margin: 40px auto; padding: 40px 36px 36px; background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.1); border-radius: 20px; box-shadow: 0 30px 80px rgba(0,0,0,0.6); backdrop-filter: blur(20px); } .onb-dev-logo { display: block; height: 52px; margin: 0 auto 28px; } .onb-title { text-align: center; font-size: 22px; font-weight: 700; color: #fff; margin-bottom: 6px; letter-spacing: -0.3px; } .onb-subtitle { text-align: center; font-size: 13px; color: rgba(255,255,255,0.5); margin-bottom: 28px; } .onb-step { display: none; } .onb-step.active { display: block; animation: fadeIn 0.35s ease; } .onb-label { display: block; font-size: 12px; font-weight: 600; color: rgba(255,255,255,0.6); text-transform: uppercase; letter-spacing: 0.8px; margin-bottom: 7px; margin-top: 18px; } .onb-input { width: 100%; padding: 12px 14px; background: rgba(255,255,255,0.07); border: 1px solid rgba(255,255,255,0.15); border-radius: 10px; font-size: 15px; color: #fff; transition: border-color 0.2s, box-shadow 0.2s; box-sizing: border-box; } .onb-input:focus { outline: none; border-color: rgba(255,255,255,0.4); box-shadow: 0 0 0 3px rgba(255,255,255,0.06); } .onb-color-row { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 14px; margin-top: 6px; } .onb-color-block label { display: block; font-size: 11px; color: rgba(255,255,255,0.5); margin-bottom: 6px; } .onb-color-preview { display: flex; align-items: center; gap: 10px; background: rgba(255,255,255,0.07); border: 1px solid rgba(255,255,255,0.15); border-radius: 10px; padding: 8px 10px; } .onb-color-preview input[type="color"] { width: 32px; height: 32px; border: none; background: none; cursor: pointer; border-radius: 6px; padding: 0; } .onb-color-preview input[type="text"] { background: none; border: none; color: #fff; font-size: 13px; width: 80px; } .onb-color-preview input[type="text"]:focus { outline: none; } .onb-logo-preview { margin-top: 14px; text-align: center; } .onb-logo-preview img { max-height: 70px; max-width: 100%; border-radius: 8px; display: none; } .onb-logo-initials { display: inline-flex; align-items: center; justify-content: center; width: 72px; height: 72px; border-radius: 50%; font-size: 26px; font-weight: 700; color: #fff; background: linear-gradient(135deg, #2d5016, #4a7c2a); display: none; } .onb-nav { display: flex; gap: 12px; margin-top: 28px; } .onb-btn { flex: 1; padding: 13px; border: none; border-radius: 10px; font-size: 15px; font-weight: 600; cursor: pointer; transition: all 0.2s; } .onb-btn-primary { background: linear-gradient(135deg, #4a7c2a, #2d5016); color: #fff; } .onb-btn-primary:hover { filter: brightness(1.1); transform: translateY(-1px); } .onb-btn-secondary { background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.7); border: 1px solid rgba(255,255,255,0.12); } .onb-btn-secondary:hover { background: rgba(255,255,255,0.12); } .onb-progress { display: flex; justify-content: center; gap: 8px; margin-bottom: 28px; } .onb-dot { width: 8px; height: 8px; border-radius: 50%; background: rgba(255,255,255,0.2); transition: background 0.3s; } .onb-dot.active { background: #4a7c2a; transform: scale(1.3); } .onb-info { font-size: 12px; color: rgba(255,255,255,0.4); margin-top: 10px; line-height: 1.5; } /* Loading screen */ #loadingScreen { display: none; position: fixed; inset: 0; background: linear-gradient(135deg, #0f0f1a, #1a1a2e); z-index: 9998; align-items: center; justify-content: center; flex-direction: column; gap: 18px; } #loadingScreen.active { display: flex; } .loading-spinner { width: 44px; height: 44px; border: 3px solid rgba(255,255,255,0.1); border-top-color: #4a7c2a; border-radius: 50%; animation: spin 0.8s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } .loading-text { color: rgba(255,255,255,0.6); font-size: 15px; } </style> </head> <body> <!-- ==================== TELA DE CARREGAMENTO ==================== --> <div id="loadingScreen" class="active"> <img src="https://eduardocm.<?> style="height:60px;opacity:0.9;" alt="Logo" onerror="this.style.display='none'"> <div class="loading-spinner"></div> <div class="loading-text">Verificando configuração...</div> </div> <!-- ==================== ONBOARDING ==================== --> <div id="onboardingOverlay"> <div class="onb-container"> <img src="https://eduardocm.<?> class="onb-dev-logo" alt="Logo Desenvolvedor" onerror="this.style.display='none'"> <div class="onb-title">Bem-vindo ao Sistema!</div> <div class="onb-subtitle">Configure sua empresa em poucos passos para começar a usar.</div> <div class="onb-progress"> <div class="onb-dot active" id="dot0"></div> <div class="onb-dot" id="dot1"></div> <div class="onb-dot" id="dot2"></div> <div class="onb-dot" id="dot3"></div> <div class="onb-dot" id="dot4"></div> </div> <!-- PASSO 1: Nome da empresa --> <div class="onb-step active" id="step0"> <span class="onb-label">Passo 1 de 4 — Sua empresa</span> <label class="onb-label" style="margin-top:14px;">Nome da empresa *</label> <input type="text" class="onb-input" id="onbNomeEmpresa" placeholder="Ex: Agro Brasil Ltda" autocomplete="off"> <label class="onb-label">Título do sistema</label> <input type="text" class="onb-input" id="onbTituloSistema" placeholder="Ex: Sistema de Pagamento de Lenha"> <div class="onb-nav"> <button class="onb-btn onb-btn-primary" onclick="onbProximo(1)">Próximo →</button> </div> </div> <!-- PASSO 2: Logo --> <div class="onb-step" id="step1"> <span class="onb-label">Passo 2 de 4 — Logomarca</span> <label class="onb-label" style="margin-top:14px;">URL da logomarca <span style="color:rgba(255,255,255,0.35);font-weight:400;">(opcional)</span></label> <input type="url" class="onb-input" id="onbLogoUrl" placeholder="https://suaempresa.com/logo.png" oninput="onbPreviewLogo()"> <div class="onb-logo-preview"> <img id="onbLogoImg" alt="Preview da logo"> <div class="onb-logo-initials" id="onbLogoInitials"></div> </div> <p class="onb-info">Se não informar uma URL, será gerado automaticamente um ícone com as iniciais do nome da empresa.</p> <div class="onb-nav"> <button class="onb-btn onb-btn-secondary" onclick="onbVoltar(0)">← Voltar</button> <button class="onb-btn onb-btn-primary" onclick="onbProximo(2)">Próximo →</button> </div> </div> <!-- PASSO 3: Cores --> <div class="onb-step" id="step2"> <span class="onb-label">Passo 3 de 4 — Cores institucionais</span> <div class="onb-color-row" style="margin-top:14px;"> <div class="onb-color-block"> <label>Primária</label> <div class="onb-color-preview"> <input type="color" id="onbCor1" value=getCssVar('--cor-primaria') oninput="syncColorText('onbCor1','onbCor1txt')"> <input type="text" id="onbCor1txt" value=getCssVar('--cor-primaria') maxlength="7" oninput="syncColorPicker('onbCor1txt','onbCor1')"> </div> </div> <div class="onb-color-block"> <label>Secundária</label> <div class="onb-color-preview"> <input type="color" id="onbCor2" value="#4a7c2a" oninput="syncColorText('onbCor2','onbCor2txt')"> <input type="text" id="onbCor2txt" value="#4a7c2a" maxlength="7" oninput="syncColorPicker('onbCor2txt','onbCor2')"> </div> </div> <div class="onb-color-block"> <label>Terciária</label> <div class="onb-color-preview"> <input type="color" id="onbCor3" value="#6d4c41" oninput="syncColorText('onbCor3','onbCor3txt')"> <input type="text" id="onbCor3txt" value="#6d4c41" maxlength="7" oninput="syncColorPicker('onbCor3txt','onbCor3')"> </div> </div> </div> <div class="onb-nav" style="margin-top:28px;"> <button class="onb-btn onb-btn-secondary" onclick="onbVoltar(1)">← Voltar</button> <button class="onb-btn onb-btn-primary" onclick="onbProximo(3)">Próximo →</button> </div> </div> <!-- PASSO 4: Empresas pagadoras --> <div class="onb-step" id="step3"> <span class="onb-label">Passo 4 de 5 — Empresas pagadoras</span> <p class="onb-info" style="margin-top:14px;">Informe as empresas que realizarão pagamentos. Você pode adicionar mais depois diretamente na aba <strong>Contadores</strong> da planilha.</p> <div id="onbEmpresasList" style="margin-top:14px;display:flex;flex-direction:column;gap:10px;"></div> <button type="button" onclick="onbAdicionarEmpresa()" style="margin-top:12px;background:rgba(255,255,255,0.07);border:1px dashed rgba(255,255,255,0.25);border-radius:10px;padding:10px;color:rgba(255,255,255,0.6);font-size:13px;width:100%;cursor:pointer;">+ Adicionar empresa</button> <div class="onb-nav"> <button class="onb-btn onb-btn-secondary" onclick="onbVoltar(2)">← Voltar</button> <button class="onb-btn onb-btn-primary" onclick="onbProximo(4)">Próximo →</button> </div> </div> <!-- PASSO 5: Acesso inicial --> <div class="onb-step" id="step4"> <span class="onb-label">Passo 5 de 5 — Dados de acesso inicial</span> <label class="onb-label" style="margin-top:14px;">Usuário *</label> <input type="text" class="onb-input" id="onbLogin" placeholder="Ex: admin" autocomplete="off"> <label class="onb-label">Senha *</label> <input type="password" class="onb-input" id="onbSenha" placeholder="Crie uma senha segura"> <p class="onb-info">Esses dados serão usados para o primeiro acesso ao sistema.</p> <div class="onb-nav"> <button class="onb-btn onb-btn-secondary" onclick="onbVoltar(3)">← Voltar</button> <button class="onb-btn onb-btn-primary" id="onbBtnFinalizar" onclick="onbFinalizar()">✓ Finalizar configuração</button> </div> </div> </div> </div> <div class="header"> <div class="header-content"> <div id="headerLogoContainer"> <!-- Logo dinâmica: preenchida via applyConfig() --> </div> <h1 id="headerTitulo">Sistema de Pagamento de Lenha</h1> </div> </div> <div class="tab-buttons"> <button class="tab-button active" onclick="openTab('cadastro')">Cadastro Fornecedor</button> <button class="tab-button" onclick="openTab('lancamento')">Lançamento</button> <button class="tab-button" onclick="openTab('financeiro')">Financeiro</button> <button class="tab-button" onclick="openTab('dashboard')">Dashboard</button> </div> <div class="tabs"> <div id="cadastro" class="tab-content active"> <h2 style="color: #4a7c2a; margin-bottom: 25px;">Cadastro de Fornecedor</h2> <div class="success-message" id="successCadastro"></div> <div class="error-message" id="errorCadastro"></div> <form id="formCadastro"> <div class="form-row"> <div class="form-group"> <label>Nome: *</label> <input type="text" id="nomeFornecedor" required> </div> <div class="form-group"> <label>CPF: *</label> <input type="text" id="cpfFornecedor" required maxlength="14" placeholder="000.000.000-00"> </div> </div> <div class="form-row"> <div class="form-group"> <label>Veículo: *</label> <input type="text" id="veiculoFornecedor" required> </div> <div class="form-group"> <label>Placa: *</label> <input type="text" id="placaFornecedor" required maxlength="8" placeholder="ABC-1234"> </div> </div> <div class="button-group"> <button type="submit" class="btn btn-primary">Salvar Fornecedor</button> <button type="button" class="btn btn-outline" onclick="limparFormCadastro()">Limpar</button> </div> </form> </div> <div id="lancamento" class="tab-content"> <div class="section-header"> <h2>Lançamento de Entrega</h2> </div> <div class="success-message" id="successLancamento"></div> <div class="error-message" id="errorLancamento"></div> <form id="formLancamento"> <div class="form-row"> <div class="form-group"> <label>Fornecedor: *</label> <select id="fornecedorLanc" onchange="carregarVeiculosEUltimaCompra()" required> <option value="">Selecione...</option> </select> </div> <div class="form-group"> <label>Veículo: *</label> <select id="veiculoLanc" onchange="preencherUltimaCompra()" required> <option value="">Selecione o fornecedor primeiro</option> </select> </div> <div class="form-group"> <label>Tipo de Lançamento: *</label> <div class="radio-group"> <div class="radio-option"> <input type="radio" id="radioQtde" name="tipoLancamento" value="Qtde (m3)" checked onchange="alternarTipoLancamento()"> <label for="radioQtde">Qtde (m³)</label> </div> <div class="radio-option"> <input type="radio" id="radioPeso" name="tipoLancamento" value="Peso (Kg)" onchange="alternarTipoLancamento()"> <label for="radioPeso">Peso (Kg)</label> </div> </div> </div> </div> <div class="form-row"> <div class="form-group"> <label>Empresa Pagadora: *</label> <select id="empresa" onchange="atualizarCodigo()" required> <option value="">Carregando...</option> </select> </div> <div class="form-group"> <label>Data: *</label> <input type="date" id="dataLancamento" required> </div> <div class="form-group"> <label>Hora: *</label> <input type="time" id="horaLancamento" value="08:00" required> </div> </div> <div class="form-row"> <div class="form-group"> <label>Produto: *</label> <select id="produtoLanc" onchange="verificarNovoProduto()" required> <option value="">Carregando...</option> </select> </div> <div class="form-group" id="grupoCampoQuantidade"> <label>Quantidade (m³): *</label> <input type="number" id="quantidadeLanc" step="0.01" min="0"> </div> <div class="form-group" id="grupoCampoPeso" style="display:none;"> <label>Peso (Kg): *</label> <input type="text" id="pesoLanc" oninput="aplicarMascaraPeso(event);"> </div> <div class="form-group" id="grupoCampoValorUnit" style="display:none;"> <label>Valor Unit. (Kg): *</label> <input type="text" id="valorUnitKgLanc" oninput="aplicarMascaraMoeda(event); calcularValorTotalPorPeso();"> </div> <div class="form-group"> <label>Valor Total (R$): *</label> <input type="text" id="valorLanc" required oninput="aplicarMascaraMoeda(event)"> </div> </div> <div class="form-row"> <div class="form-group"> <label>Local de Entrega: *</label> <select id="localLanc" onchange="verificarNovoLocal()" required> <option value="">Carregando...</option> </select> </div> <div class="form-group" id="grupoCampoPedido" style="display:none;"> <label>Pedido: *</label> <input type="text" id="pedidoLanc" placeholder="000.000" maxlength="6" oninput="this.value = this.value.replace(/\D/g, '')" onblur="aplicarMascaraPedido(event)"> </div> <div class="form-group"> <label>Observação:</label> <textarea id="observacaoLanc" rows="3"></textarea> </div> </div> <div class="button-group"> <button type="submit" class="btn btn-primary" id="btnSalvar">Salvar</button> <button type="button" class="btn btn-outline" onclick="limparFormLancamento()">Limpar</button> </div> </form> </div> <div id="financeiro" class="tab-content"> <h2 style="color: #4a7c2a; margin-bottom: 25px;">Controle Financeiro</h2> <div class="filter-section"> <h3 style="color: #4a7c2a; margin-bottom: 15px;">Filtros de Pesquisa</h3> <div class="form-row"> <div class="form-group"> <label>Fornecedor:</label> <select id="filtroFornecedor"> <option value="">Todos</option> </select> </div> <div class="form-group"> <label>Código:</label> <input type="text" id="filtroCodigo" placeholder="Ex: P-1001"> </div> <div class="form-group"> <label>Nº Pedido:</label> <input type="text" id="filtroPedido" placeholder="000.000" onblur="aplicarMascaraPedido(event)"> </div> <div class="form-group"> <label>Local de Entrega:</label> <select id="filtroLocal"> <option value="">Todos</option> </select> </div> </div> <div class="form-row"> <div class="form-group"> <label>Status de NF:</label> <select id="filtroStatusNF"> <option value="">Todos</option> <option value="EM ABERTO">EM ABERTO</option> <option value="EMITIR NF">EMITIR NF</option> <option value="A PAGAR">A PAGAR</option> <option value="FINALIZADO">FINALIZADO</option> </select> </div> <div class="form-group"> <label>Data Início:</label> <input type="date" id="filtroDataInicio"> </div> <div class="form-group"> <label>Data Fim:</label> <input type="date" id="filtroDataFim"> </div> <div class="form-group"> <label>Nº NF:</label> <input type="text" id="filtroNumeroNF" placeholder="Ex: 12345"> </div> </div> <div style="display:flex; gap:10px; margin-top:10px; align-items: center;"> <button class="btn btn-primary" onclick="aplicarFiltrosFinanceiro()">Pesquisar</button> <button class="btn btn-secondary" onclick="limparFiltrosFinanceiro()">Limpar Filtros</button> <button class="btn-imprimir-tabela" onclick="imprimirTabelaFinanceiro()"> <span>🖨️</span> Imprimir </button> <div class="resultados-por-pagina"> <label>Resultados por Página:</label> <select id="resultadosPorPagina" onchange="aplicarFiltrosFinanceiro()"> <option value="10" selected>10</option> <option value="15">15</option> <option value="30">30</option> <option value="50">50</option> <option value="100">100</option> <option value="999999">Todos</option> </select> </div> </div> </div> <div class="table-container"> <table> <thead> <tr> <th style="border-left: none;">Ações</th> <th data-coluna="status" onclick="ordenarTabela('status')">Status</th> <th data-coluna="pagamento" onclick="ordenarTabela('pagamento')" class="group-border-left">Pagamento</th> <th data-coluna="nf" onclick="ordenarTabela('nf')">NF</th> <th data-coluna="pedido" onclick="ordenarTabela('pedido')">Pedido</th> <th data-coluna="data" onclick="ordenarTabela('data')" class="group-border-left">Data</th> <th data-coluna="hora" onclick="ordenarTabela('hora')">Hora</th> <th data-coluna="local" onclick="ordenarTabela('local')">Local</th> <th data-coluna="codigo" onclick="ordenarTabela('codigo')">Código</th> <th data-coluna="fornecedor" onclick="ordenarTabela('fornecedor')" class="group-border-left">Fornecedor</th> <th data-coluna="produto" onclick="ordenarTabela('produto')">Produto</th> <th data-coluna="veiculo" onclick="ordenarTabela('veiculo')">Veículo</th> <th data-coluna="placa" onclick="ordenarTabela('placa')">Placa</th> <th data-coluna="quantidade" onclick="ordenarTabela('quantidade')" class="group-border-left">Qtd (m³)</th> <th data-coluna="peso" onclick="ordenarTabela('peso')">Qtd (Kg)</th> <th data-coluna="valor" onclick="ordenarTabela('valor')" class="group-border-left">Valor Total</th> <th>Obs</th> </tr> </thead> <tbody id="tabelaFinanceiro"> <tr> <td colspan="17" class="loading">Carregando dados...</td> </tr> </tbody> </table> </div> </div> <div id="dashboard" class="tab-content"> <h2 style="color: #4a7c2a; margin-bottom: 25px;">Dashboard</h2> <div class="filter-section"> <h3 style="color: #4a7c2a; margin-bottom: 15px;">Filtros de Pesquisa</h3> <div class="form-row"> <div class="form-group"> <label>Fornecedor:</label> <select id="dashFornecedor"> <option value="">Todos</option> </select> </div> <div class="form-group"> <label>Código:</label> <input type="text" id="dashCodigo" placeholder="Ex: P-1001"> </div> <div class="form-group"> <label>Nº Pedido:</label> <input type="text" id="dashPedido" placeholder="000.000" onblur="aplicarMascaraPedido(event)"> </div> <div class="form-group"> <label>Local de Entrega:</label> <select id="dashLocal"> <option value="">Todos</option> </select> </div> </div> <div class="form-row"> <div class="form-group"> <label>Status de NF:</label> <select id="dashStatusNF"> <option value="">Todos</option> <option value="EM ABERTO">EM ABERTO</option> <option value="EMITIR NF">EMITIR NF</option> <option value="A PAGAR">A PAGAR</option> <option value="FINALIZADO">FINALIZADO</option> </select> </div> <div class="form-group"> <label>Data Início:</label> <input type="date" id="dashDataInicio"> </div> <div class="form-group"> <label>Data Fim:</label> <input type="date" id="dashDataFim"> </div> <div class="form-group"> <label>Nº NF:</label> <input type="text" id="dashNumeroNF" placeholder="Ex: 12345" maxlength="8" oninput="this.value = this.value.replace(/\D/g, '')"> </div> </div> <div style="display:flex; gap:10px; margin-top:10px;"> <button class="btn btn-primary" onclick="atualizarDashboard()">Atualizar Dashboard</button> <button class="btn btn-secondary" onclick="limparFiltrosDashboard()">Limpar Filtros</button> <button class="btn-imprimir-tabela" onclick="imprimirDashboard()"> <span>🖨️</span> Imprimir </button> </div> </div> <div class="dashboard-cards" style="grid-template-columns: repeat(4, 1fr);"> <div class="card"> <h3>Qtde Recebida (Kg)</h3> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 10px;"> <div style="text-align: center;"> <div style="font-size: 12px; color: #a5a19e; margin-bottom: 5px;">Lenha</div> <div class="value" style="font-size: 22px;" id="qtdeLenha">0</div> </div> <div style="text-align: center;"> <div style="font-size: 12px; color: #a5a19e; margin-bottom: 5px;">Casca de Arroz</div> <div class="value" style="font-size: 22px;" id="qtdeCascaArroz">0</div> </div> </div> </div> <div class="card"> <h3>Valor Total (R$)</h3> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 10px;"> <div style="text-align: center;"> <div style="font-size: 12px; color: #a5a19e; margin-bottom: 5px;">Lenha</div> <div class="value" style="font-size: 22px;" id="valorLenha">0,00</div> </div> <div style="text-align: center;"> <div style="font-size: 12px; color: #a5a19e; margin-bottom: 5px;">Casca de Arroz</div> <div class="value" style="font-size: 22px;" id="valorCascaArroz">0,00</div> </div> </div> </div> <div class="card"> <h3>Total Pago (R$)</h3> <div class="value" id="totalPago">0,00</div> </div> <div class="card"> <h3>Total a Pagar (R$)</h3> <div class="value" id="totalAPagar">0,00</div> </div> </div> <div class="form-row"> <div class="chart-container"> <h3>Quantidade por Local de Entrega (Kg)</h3> <canvas id="chartLocal"></canvas> </div> <div class="chart-container"> <h3>Quantidade por Produto (Kg)</h3> <canvas id="chartProduto"></canvas> </div> </div> <div class="form-row"> <div class="chart-container"> <h3>Quantidade de Compras por Empresa - Últimos 6 Meses (Kg)</h3> <canvas id="chartEmpresaQtd"></canvas> </div> <div class="chart-container"> <h3>Valor Total por Empresa - Últimos 6 Meses</h3> <canvas id="chartEmpresaValor"></canvas> </div> </div> <div class="form-row"> <div class="chart-container"> <h3>Quantidade por Fornecedor (Kg)</h3> <canvas id="chartFornecedorQtd"></canvas> </div> <div class="chart-container"> <h3>Valor Total por Fornecedor</h3> <canvas id="chartFornecedorValor"></canvas> </div> </div> <div class="form-row"> <div class="chart-container"> <h3>Variação de Preço Médio da Lenha (Últimos 12 Meses)</h3> <canvas id="chartPrecoLenha"></canvas> </div> <div class="chart-container"> <h3>Variação de Preço Médio da Casca de Arroz (Últimos 12 Meses)</h3> <canvas id="chartPrecoCasca"></canvas> </div> </div> </div> </div> <div id="modalPagamento" class="modal"> <div class="modal-content"> <div class="modal-header"> <span class="close-modal" onclick="fecharModalPagamento()">&times;</span> <h2>Confirmar Pagamento</h2> </div> <form id="formPagamento"> <input type="hidden" id="codigoPagamento"> <div class="form-group"> <label>Data do Pagamento:</label> <input type="date" id="dataPagamento" required> </div> <div class="form-group"> <label>Tipo de Pagamento:</label> <select id="tipoPagamento" onchange="atualizarNumeroPagamento()" required> <option value="">Selecione...</option> <option value="Cheque">Cheque</option> <option value="Transferência">Transferência</option> </select> </div> <div class="form-group"> <label>Nº de Cheque / Transferência:</label> <input type="text" id="numeroPagamento" placeholder="Digite o número do cheque ou TED" maxlength="6" oninput="this.value = this.value.replace(/\D/g, '')" required> </div> <div class="form-group"> <label>Observações:</label> <textarea id="observacaoPagamento" rows="3"></textarea> </div> <div class="button-group"> <button type="submit" class="btn btn-primary">Confirmar</button> <button type="button" class="btn btn-outline" onclick="fecharModalPagamento()">Cancelar</button> </div> </form> </div> </div> <div id="modalNotaFiscal" class="modal"> <div class="modal-content"> <div class="modal-header"> <span class="close-modal" onclick="fecharModalNotaFiscal()">&times;</span> <h2>Registrar Nota Fiscal</h2> </div> <form id="formNotaFiscal"> <input type="hidden" id="codigoNotaFiscal"> <div class="form-group"> <label>Data de Emissão: *</label> <input type="date" id="dataEmissaoNF" required> </div> <div class="form-group"> <label>Número da NF: *</label> <input type="text" id="numeroNF" maxlength="8" oninput="this.value = this.value.replace(/\D/g, '')" required placeholder="Ex: 12345"> </div> <div class="form-group"> <label>Observações:</label> <textarea id="observacaoNF" rows="3"></textarea> </div> <div class="button-group"> <button type="submit" class="btn btn-primary">Confirmar</button> <button type="button" class="btn btn-outline" onclick="fecharModalNotaFiscal()">Cancelar</button> </div> </form> </div> </div> <script src="https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.0.0"></script> <script> let fornecedoresCache = []; let lancamentosCache = []; let dadosImpressao = null; let ultimasComprasCache = {}; let financeiroCarregado = false; let empresaPorFornecedor = {}; const CONVERSAO_M3_PARA_KG = 285.7142857142857; function formatarValorReal(valor) { return 'R$ ' + parseFloat(valor).toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } function formatarNumero(numero) { return parseFloat(numero).toLocaleString('pt-BR', { minimumFractionDigits: 0, maximumFractionDigits: 0 }); } function formatarPedido(numero) { if (!numero) return ''; // Remove tudo que não é número const num = numero.toString().replace(/\D/g, ''); // Adiciona zeros à esquerda se necessário const padded = num.padStart(6, '0'); // Formata com separador de milhar return padded.slice(0, 3) + '.' + padded.slice(3); } Chart.register(ChartDataLabels); if (typeof ChartDataLabels !== 'undefined') { Chart.register(ChartDataLabels); } function formatarPedidoTabela(pedido) { if (!pedido) return '-'; // Remove tudo que não é número ou letra const limpo = pedido.toString().replace(/[^A-Za-z0-9]/g, ''); // Se começar com letra (ex: P-1001, D-1001), manter formato original if (/^[A-Za-z]/.test(limpo)) { return pedido; } // Se for apenas números, formatar com separador de milhar const numero = parseInt(limpo, 10); if (!isNaN(numero)) { return formatarNumero(numero); } return pedido; } function openTab(tabName) { const contents = document.querySelectorAll('.tab-content'); const buttons = document.querySelectorAll('.tab-button'); contents.forEach(content => content.classList.remove('active')); buttons.forEach(button => button.classList.remove('active')); document.getElementById(tabName).classList.add('active'); event.target.classList.add('active'); if (tabName === 'lancamento') { carregarDadosLancamento(); } else if (tabName === 'financeiro') { carregarDadosFinanceiro(); } else if (tabName === 'dashboard') { carregarDadosDashboard(); } } window._appConfig = null; // Configuração global da empresa window.onload = function() { // Mostrar loading e verificar se sistema já foi configurado document.getElementById('loadingScreen').classList.add('active'); google.script.run .withSuccessHandler(function(configurado) { if (configurado) { // Sistema já configurado — carregar configurações e iniciar normalmente google.script.run .withSuccessHandler(function(config) { document.getElementById('loadingScreen').classList.remove('active'); if (config) { window._appConfig = config; applyConfig(config); } iniciarSistema(); }) .withFailureHandler(function() { document.getElementById('loadingScreen').classList.remove('active'); iniciarSistema(); }) .obterConfiguracaoEmpresa(); } else { // Sistema não configurado — mostrar onboarding document.getElementById('loadingScreen').classList.remove('active'); document.getElementById('onboardingOverlay').classList.add('active'); onbAtualizarPreviewInicial(); } }) .withFailureHandler(function() { // Em caso de erro na verificação, iniciar normalmente document.getElementById('loadingScreen').classList.remove('active'); iniciarSistema(); }) .verificarSistemaConfigurado(); }; /** Inicializa o sistema principal (comportamento original) */ function iniciarSistema() { const hoje = new Date().toISOString().split('T')[0]; document.getElementById('dataLancamento').value = hoje; document.getElementById('dataPagamento').value = hoje; document.getElementById('dataEmissaoNF').value = hoje; carregarFornecedores(); setTimeout(function() { document.querySelector('.tab-button:nth-child(2)').click(); }, 100); } /** Aplica as configurações da empresa na interface */ function applyConfig(config) { // Título do sistema var titulo = config.tituloSistema || config.nomeEmpresa || 'Sistema de Pagamento de Lenha'; document.getElementById('headerTitulo').textContent = titulo; document.title = titulo; // Logo no header var logoContainer = document.getElementById('headerLogoContainer'); if (config.logoUrl && config.logoUrl.trim() !== '') { logoContainer.innerHTML = '<img src="' + config.logoUrl + '" alt="Logo" class="logo" onerror="this.style.display=\'none\'">'; window._logoUrl = config.logoUrl; } else { // Gerar logo com iniciais var iniciais = gerarIniciais(config.nomeEmpresa || ''); var bgColor = config.corPrimaria || getCssVar('--cor-primaria'); logoContainer.innerHTML = '<div style="width:50px;height:50px;border-radius:50%;background:' + bgColor + ';display:flex;align-items:center;justify-content:center;font-weight:700;font-size:18px;color:#fff;">' + iniciais + '</div>'; window._logoUrl = ''; } // Aplicar cores diretamente nos elementos via JS (compatível com GAS/Caja) var p = config.corPrimaria || '#2d5016'; var s = config.corSecundaria || '#4a7c2a'; var t = config.corTerciaria || '#6d4c41'; var pD = shadeColor(p, -30); var sL = shadeColor(s, 20); var tD = shadeColor(t, -20); // Injetar tag <style> dinâmica com as cores escolhidas var styleEl = document.getElementById('dynamicColors'); if (!styleEl) { styleEl = document.createElement('style'); styleEl.id = 'dynamicColors'; document.head.appendChild(styleEl); } styleEl.textContent = [ '.header { background: linear-gradient(135deg, ' + p + ' 0%, ' + s + ' 100%) !important; }', '.tab-buttons { background: rgba(109,76,65,0.85); border-bottom: 2px solid ' + t + ' !important; }', '.tab-button.active::after { background: ' + s + ' !important; }', '.tab-button:hover { background: rgba(141,110,99,0.5); }', '.tab-button.active { background: rgba(93,64,55,0.7); }', '.section-header h2, .modal-header h2, .form-group label, .resultados-por-pagina label { color: ' + s + ' !important; }', '.btn-primary { background: linear-gradient(135deg, ' + p + ' 0%, ' + s + ' 100%) !important; }', '.btn-outline { color: ' + s + ' !important; border-color: ' + s + ' !important; }', '.btn-outline:hover { background: ' + s + ' !important; }', '.btn-imprimir-tabela { background: linear-gradient(135deg, ' + p + ' 0%, ' + s + ' 100%) !important; }', '.card { border-left-color: ' + s + ' !important; }', '.card .value { color: ' + s + ' !important; }', 'thead { background: linear-gradient(135deg, ' + p + ' 0%, ' + s + ' 100%) !important; }', 'tbody tr.selected { background: ' + s + '4d !important; border-left-color: ' + s + ' !important; }', '.status-pago { background: ' + p + ' !important; }', '.filter-section h3 { color: ' + s + ' !important; }', '.tab-content h2[style*="color"] { color: ' + s + ' !important; }', '#dashboard h2, #financeiro h2, #cadastro h2, #lancamento h2 { color: ' + s + ' !important; }' ].join(' '); } /** Clareia (+) ou escurece (-) uma cor hex */ function shadeColor(hex, percent) { var num = parseInt(hex.replace('#',''), 16); var r = Math.min(255, Math.max(0, (num >> 16) + Math.round(2.55 * percent))); var g = Math.min(255, Math.max(0, ((num >> 8) & 0x00FF) + Math.round(2.55 * percent))); var b = Math.min(255, Math.max(0, (num & 0x0000FF) + Math.round(2.55 * percent))); return '#' + ((1 << 24) | (r << 16) | (g << 8) | b).toString(16).slice(1); } function gerarIniciais(nome) { if (!nome) return '??'; var partes = nome.trim().split(/\s+/).filter(function(p) { return p.length > 0; }); if (partes.length === 0) return '??'; if (partes.length === 1) return partes[0].substring(0, 2).toUpperCase(); return (partes[0][0] + partes[partes.length-1][0]).toUpperCase(); } /* ==================== ONBOARDING FUNCTIONS ==================== */ var onbStep = 0; function onbAtualizarDots(step) { for (var i = 0; i < 5; i++) { var dot = document.getElementById('dot' + i); if (dot) dot.classList.toggle('active', i === step); } } function onbProximo(targetStep) { // Validações por passo if (targetStep === 1) { var nome = document.getElementById('onbNomeEmpresa').value.trim(); if (!nome) { alert('Por favor, informe o nome da empresa.'); return; } } // Validação passo empresas: deve ter ao menos 1 empresa if (targetStep === 4) { var empresaInputs = document.querySelectorAll('.onb-empresa-input'); var temEmpresa = false; empresaInputs.forEach(function(inp) { if (inp.value.trim()) temEmpresa = true; }); if (!temEmpresa) { alert('Adicione ao menos uma empresa pagadora.'); return; } } document.getElementById('step' + onbStep).classList.remove('active'); onbStep = targetStep; document.getElementById('step' + onbStep).classList.add('active'); onbAtualizarDots(onbStep); if (onbStep === 1) { onbAtualizarPreviewLogo(); } } function onbVoltar(targetStep) { document.getElementById('step' + onbStep).classList.remove('active'); onbStep = targetStep; document.getElementById('step' + onbStep).classList.add('active'); onbAtualizarDots(onbStep); } function onbAtualizarPreviewInicial() { // Inicializa preview da logo com iniciais onbPreviewLogo(); } function onbPreviewLogo() { var url = (document.getElementById('onbLogoUrl') || {}).value || ''; var nomeEmpresa = (document.getElementById('onbNomeEmpresa') || {}).value || ''; var imgEl = document.getElementById('onbLogoImg'); var initEl = document.getElementById('onbLogoInitials'); if (!imgEl || !initEl) return; if (url.trim() !== '') { imgEl.src = url; imgEl.style.display = 'block'; initEl.style.display = 'none'; } else { imgEl.style.display = 'none'; var iniciais = gerarIniciais(nomeEmpresa); initEl.textContent = iniciais; initEl.style.display = 'inline-flex'; } } function onbAtualizarPreviewLogo() { onbPreviewLogo(); } function syncColorText(pickerId, textId) { var picker = document.getElementById(pickerId); var text = document.getElementById(textId); if (picker && text) text.value = picker.value; } function syncColorPicker(textId, pickerId) { var text = document.getElementById(textId); var picker = document.getElementById(pickerId); if (text && picker && /^#[0-9A-Fa-f]{6}$/.test(text.value)) { picker.value = text.value; } } function onbFinalizar() { var login = document.getElementById('onbLogin').value.trim(); var senha = document.getElementById('onbSenha').value.trim(); if (!login || !senha) { alert('Por favor, informe o usuário e a senha de acesso.'); return; } var btn = document.getElementById('onbBtnFinalizar'); btn.disabled = true; btn.textContent = 'Salvando...'; // Coletar empresas pagadoras var empresasInputs = document.querySelectorAll('.onb-empresa-input'); var empresasList = []; empresasInputs.forEach(function(inp) { var v = inp.value.trim(); if (v) empresasList.push(v); }); var dados = { nomeEmpresa: document.getElementById('onbNomeEmpresa').value.trim(), tituloSistema: document.getElementById('onbTituloSistema').value.trim(), logoUrl: document.getElementById('onbLogoUrl').value.trim(), corPrimaria: document.getElementById('onbCor1').value, corSecundaria: document.getElementById('onbCor2').value, corTerciaria: document.getElementById('onbCor3').value, loginUsuario: login, loginSenha: senha, empresas: empresasList }; google.script.run .withSuccessHandler(function(result) { if (result && result.sucesso) { window._appConfig = dados; applyConfig(dados); document.getElementById('onboardingOverlay').classList.remove('active'); iniciarSistema(); } else { alert('Erro ao salvar: ' + (result ? result.mensagem : 'Erro desconhecido')); btn.disabled = false; btn.textContent = '✓ Finalizar configuração'; } }) .withFailureHandler(function(err) { alert('Erro ao salvar configuração: ' + err); btn.disabled = false; btn.textContent = '✓ Finalizar configuração'; }) .salvarConfiguracaoInicial(dados); } function onbAdicionarEmpresa() { var list = document.getElementById('onbEmpresasList'); var idx = list.children.length; var row = document.createElement('div'); row.style.cssText = 'display:flex;gap:8px;align-items:center;'; row.innerHTML = '<input type="text" class="onb-input onb-empresa-input" placeholder="Nome da empresa" style="flex:1;" />' + '<button type="button" onclick="this.parentElement.remove()" style="background:rgba(255,80,80,0.2);border:1px solid rgba(255,80,80,0.3);border-radius:8px;padding:10px 14px;color:#ff8080;cursor:pointer;font-size:16px;">✕</button>'; list.appendChild(row); // Focus new input row.querySelector('input').focus(); } /** Obtém valor de uma variável CSS do :root */ function getCssVar(varName) { return getComputedStyle(document.documentElement).getPropertyValue(varName).trim() || varName; } /* ==================== FIM ONBOARDING ==================== */ document.getElementById('formCadastro').addEventListener('submit', function(e) { e.preventDefault(); const btnSalvar = e.target.querySelector('button[type="submit"]'); btnSalvar.disabled = true; const dados = { nome: document.getElementById('nomeFornecedor').value, cpf: document.getElementById('cpfFornecedor').value, veiculo: document.getElementById('veiculoFornecedor').value, placa: document.getElementById('placaFornecedor').value }; google.script.run.withSuccessHandler(function(msg) { mostrarMensagem('successCadastro', msg); limparFormCadastro(); carregarFornecedores(); btnSalvar.disabled = false; }).withFailureHandler(function(error) { mostrarMensagem('errorCadastro', 'Erro ao salvar: ' + error); btnSalvar.disabled = false; }).salvarFornecedor(dados); }); function limparFormCadastro() { document.getElementById('formCadastro').reset(); } function carregarFornecedores() { google.script.run.withSuccessHandler(function(fornecedores) { fornecedoresCache = fornecedores || []; const selectLanc = document.getElementById('fornecedorLanc'); const selectFinanceiro = document.getElementById('filtroFornecedor'); const selectDash = document.getElementById('dashFornecedor'); selectLanc.innerHTML = '<option value="">Selecione...</option>'; selectFinanceiro.innerHTML = '<option value="">Todos</option>'; selectDash.innerHTML = '<option value="">Todos</option>'; const fornecedoresUnicos = [...new Set(fornecedores.map(f => f.nome))]; fornecedoresUnicos.forEach(nome => { selectLanc.innerHTML += `<option value="${nome}">${nome}</option>`; selectFinanceiro.innerHTML += `<option value="${nome}">${nome}</option>`; selectDash.innerHTML += `<option value="${nome}">${nome}</option>`; }); }).listarFornecedores(); } function carregarDadosLancamento() { // Carregar empresas da planilha Contadores google.script.run.withSuccessHandler(function(empresas) { var selectEmpresa = document.getElementById('empresa'); selectEmpresa.innerHTML = '<option value="">Selecione...</option>'; (empresas || []).forEach(function(emp) { selectEmpresa.innerHTML += '<option value="' + emp + '">' + emp + '</option>'; }); }).listarEmpresas(); google.script.run.withSuccessHandler(function(produtos) { const select = document.getElementById('produtoLanc'); select.innerHTML = '<option value="">Selecione...</option>'; produtos.forEach(p => { select.innerHTML += `<option value="${p}">${p}</option>`; }); select.innerHTML += '<option value="+">+ Adicionar novo produto</option>'; }).listarProdutos(); google.script.run.withSuccessHandler(function(locais) { const select = document.getElementById('localLanc'); select.innerHTML = '<option value="">Selecione...</option>'; locais.forEach(l => { select.innerHTML += `<option value="${l}">${l}</option>`; }); select.innerHTML += '<option value="+">+ Adicionar novo local</option>'; }).listarLocais(); if (fornecedoresCache.length === 0) { carregarFornecedores(); } } function verificarNovoProduto() { const select = document.getElementById('produtoLanc'); if (select.value === '+') { const produto = prompt('Digite o nome do novo produto:'); if (produto && produto.trim()) { google.script.run.withSuccessHandler(function(msg) { alert(msg); carregarDadosLancamento(); }).adicionarProduto(produto.trim()); } else { select.value = ''; } } } function verificarNovoLocal() { const select = document.getElementById('localLanc'); if (select.value === '+') { const local = prompt('Digite o nome do novo local:'); if (local && local.trim()) { google.script.run.withSuccessHandler(function(msg) { alert(msg); carregarDadosLancamento(); }).adicionarLocal(local.trim()); } else { select.value = ''; } } } function atualizarCodigo() { const empresa = document.getElementById('empresa').value; if (empresa) { google.script.run.withSuccessHandler(function(codigo) { // Não exibir mais o código na parte superior }).gerarProximoCodigo(empresa); } } function alternarTipoLancamento() { const tipoPeso = document.getElementById('radioPeso').checked; // Mostrar/ocultar campos document.getElementById('grupoCampoQuantidade').style.display = tipoPeso ? 'none' : 'block'; document.getElementById('grupoCampoPeso').style.display = tipoPeso ? 'block' : 'none'; document.getElementById('grupoCampoValorUnit').style.display = tipoPeso ? 'block' : 'none'; document.getElementById('grupoCampoPedido').style.display = tipoPeso ? 'block' : 'none'; // Ajustar required document.getElementById('quantidadeLanc').required = !tipoPeso; document.getElementById('pesoLanc').required = tipoPeso; document.getElementById('valorUnitKgLanc').required = tipoPeso; document.getElementById('pedidoLanc').required = tipoPeso; // Ajustar readonly do Valor Total const campoValorTotal = document.getElementById('valorLanc'); if (tipoPeso) { campoValorTotal.readOnly = true; campoValorTotal.style.backgroundColor = 'rgba(26, 20, 16, 0.4)'; campoValorTotal.style.cursor = 'not-allowed'; } else { campoValorTotal.readOnly = false; campoValorTotal.style.backgroundColor = 'rgba(26, 20, 16, 0.6)'; campoValorTotal.style.cursor = 'text'; } } function calcularValorTotalPorPeso() { // Obter peso (remover pontos de milhar e trocar vírgula por ponto) const pesoStr = document.getElementById('pesoLanc').value; const peso = parseFloat(pesoStr.replace(/\./g, '').replace(',', '.')) || 0; // Obter valor unitário (remover pontos de milhar e trocar vírgula por ponto) const valorUnitStr = document.getElementById('valorUnitKgLanc').value; const valorUnit = parseFloat(valorUnitStr.replace(/\./g, '').replace(',', '.')) || 0; const valorTotal = peso * valorUnit; // Formatar com máscara monetária const valorFormatado = formatarMoeda(valorTotal); document.getElementById('valorLanc').value = valorFormatado; } function formatarMoeda(valor) { if (isNaN(valor)) valor = 0; return valor.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } function aplicarMascaraMoeda(event) { let valor = event.target.value; // Remove tudo que não é número valor = valor.replace(/\D/g, ''); // Converte para número valor = (parseFloat(valor) / 100).toFixed(2); // Formata valor = valor.replace('.', ','); valor = valor.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1.'); event.target.value = valor; } function aplicarMascaraPeso(event) { let valor = event.target.value; valor = valor.replace(/\D/g, ''); // mantém apenas números if (valor !== '') { valor = parseInt(valor, 10).toString(); // remove casas decimais valor = valor.replace(/\B(?=(\d{3})+(?!\d))/g, '.'); // adiciona pontos de milhar } event.target.value = valor; calcularValorTotalPorPeso(); // recalcula automático } function aplicarMascaraPedido(event) { let valor = event.target.value; // Remove tudo que não é número valor = valor.replace(/\D/g, ''); // Limita a 6 dígitos if (valor.length > 6) { valor = valor.substring(0, 6); } // Adiciona zeros à esquerda valor = valor.padStart(6, '0'); // Adiciona separador de milhar valor = valor.slice(0, 3) + '.' + valor.slice(3); event.target.value = valor; } function carregarVeiculosEUltimaCompra() { const fornecedor = document.getElementById('fornecedorLanc').value; const selectVeiculo = document.getElementById('veiculoLanc'); const selectEmpresa = document.getElementById('empresa'); if (!fornecedor) { selectVeiculo.innerHTML = '<option value="">Selecione o fornecedor primeiro</option>'; return; } if (empresaPorFornecedor[fornecedor]) { selectEmpresa.value = empresaPorFornecedor[fornecedor]; atualizarCodigo(); } const veiculosFornecedor = fornecedoresCache.filter(f => f.nome === fornecedor); if (veiculosFornecedor.length === 0) { selectVeiculo.innerHTML = '<option value="">Nenhum veículo cadastrado</option>'; return; } if (veiculosFornecedor.length === 1) { const veic = veiculosFornecedor[0].veiculo; const placa = veiculosFornecedor[0].placa; selectVeiculo.innerHTML = `<option value="${veic}|${placa}">${veic} - ${placa}</option>`; carregarUltimaCompraCache(fornecedor, veic); } else { selectVeiculo.innerHTML = '<option value="">Selecione...</option>'; veiculosFornecedor.forEach(v => { selectVeiculo.innerHTML += `<option value="${v.veiculo}|${v.placa}">${v.veiculo} - ${v.placa}</option>`; }); } } function preencherUltimaCompra() { const fornecedor = document.getElementById('fornecedorLanc').value; const veiculo = document.getElementById('veiculoLanc').value.split('|')[0]; if (!fornecedor || !veiculo) return; carregarUltimaCompraCache(fornecedor, veiculo); } function carregarUltimaCompraCache(fornecedor, veiculo) { const chaveCache = `${fornecedor}|${veiculo}`; if (ultimasComprasCache[chaveCache]) { preencherFormularioComUltimaCompra(ultimasComprasCache[chaveCache]); return; } google.script.run .withSuccessHandler(function(ultimaCompra) { if (ultimaCompra) { ultimasComprasCache[chaveCache] = ultimaCompra; if (ultimaCompra.empresa) { empresaPorFornecedor[fornecedor] = ultimaCompra.empresa; document.getElementById('empresa').value = ultimaCompra.empresa; atualizarCodigo(); } preencherFormularioComUltimaCompra(ultimaCompra); } else { preencherFormularioComUltimaCompra(null); } }) .withFailureHandler(function(error) { console.error('Erro ao carregar última compra:', error); preencherFormularioComUltimaCompra(null); }) .obterUltimaCompraComEmpresa(fornecedor, veiculo); } function preencherFormularioComUltimaCompra(ultimaCompra) { setTimeout(function() { if (ultimaCompra) { // Definir tipo de lançamento if (ultimaCompra.tipoLancamento === 'Peso (Kg)') { document.getElementById('radioPeso').checked = true; alternarTipoLancamento(); // Formatar peso com 1 casa decimal if (ultimaCompra.peso) { const pesoFormatado = parseFloat(ultimaCompra.peso).toFixed(1).replace('.', ','); document.getElementById('pesoLanc').value = pesoFormatado; } // Formatar valor unitário if (ultimaCompra.valorUnitKg) { const valorUnitFormatado = formatarMoeda(ultimaCompra.valorUnitKg); document.getElementById('valorUnitKgLanc').value = valorUnitFormatado; } // Formatar pedido if (ultimaCompra.pedido) { document.getElementById('pedidoLanc').value = formatarPedido(ultimaCompra.pedido); } } else { document.getElementById('radioQtde').checked = true; alternarTipoLancamento(); document.getElementById('quantidadeLanc').value = ultimaCompra.quantidade || ''; } document.getElementById('produtoLanc').value = ultimaCompra.produto || ''; // Formatar valor total if (ultimaCompra.valorTotal) { document.getElementById('valorLanc').value = formatarMoeda(ultimaCompra.valorTotal); } document.getElementById('localLanc').value = ultimaCompra.localEntrega || ''; document.getElementById('observacaoLanc').value = ultimaCompra.observacao || ''; } else { document.getElementById('radioQtde').checked = true; alternarTipoLancamento(); document.getElementById('produtoLanc').value = ''; document.getElementById('quantidadeLanc').value = ''; document.getElementById('valorLanc').value = ''; document.getElementById('localLanc').value = ''; document.getElementById('observacaoLanc').value = 'Primeira Compra'; } }, 50); } document.getElementById('formLancamento').addEventListener('submit', function(e) { e.preventDefault(); const btnSalvar = document.getElementById('btnSalvar'); btnSalvar.disabled = true; const veiculoPlaca = document.getElementById('veiculoLanc').value.split('|'); const fornecedor = document.getElementById('fornecedorLanc').value; const fornecedorObj = fornecedoresCache.find(f => f.nome === fornecedor); const empresa = document.getElementById('empresa').value; const tipoPeso = document.getElementById('radioPeso').checked; const dados = { empresa: empresa, data: document.getElementById('dataLancamento').value, hora: document.getElementById('horaLancamento').value, fornecedor: fornecedor, cpf: fornecedorObj ? fornecedorObj.cpf : '', veiculo: veiculoPlaca[0], placa: veiculoPlaca[1], produto: document.getElementById('produtoLanc').value, valorTotal: document.getElementById('valorLanc').value.replace(/\./g, '').replace(',', '.'), localEntrega: document.getElementById('localLanc').value, observacao: document.getElementById('observacaoLanc').value, tipoLancamento: tipoPeso ? 'Peso (Kg)' : 'Qtde (m3)' }; if (tipoPeso) { dados.peso = document.getElementById('pesoLanc').value.replace(/\./g, '').replace(',', '.'); dados.valorUnitKg = document.getElementById('valorUnitKgLanc').value.replace(/\./g, '').replace(',', '.'); dados.pedido = document.getElementById('pedidoLanc').value.replace(/\./g, ''); dados.quantidade = ''; } else { dados.quantidade = document.getElementById('quantidadeLanc').value; dados.peso = ''; dados.valorUnitKg = ''; dados.pedido = ''; } google.script.run.withSuccessHandler(function(resultado) { mostrarMensagem('successLancamento', resultado.mensagem); dadosImpressao = resultado.dadosCompletos; empresaPorFornecedor[fornecedor] = empresa; const chaveCache = `${fornecedor}|${veiculoPlaca[0]}`; delete ultimasComprasCache[chaveCache]; financeiroCarregado = false; if (confirm('Deseja imprimir as 3 vias do comprovante?')) { imprimirComprovante(resultado.dadosCompletos); } limparFormLancamento(); btnSalvar.disabled = false; }).withFailureHandler(function(error) { mostrarMensagem('errorLancamento', 'Erro ao salvar: ' + error); btnSalvar.disabled = false; }).salvarLancamento(dados); }); function limparFormLancamento() { document.getElementById('formLancamento').reset(); document.getElementById('horaLancamento').value = '08:00'; const hoje = new Date().toISOString().split('T')[0]; document.getElementById('dataLancamento').value = hoje; document.getElementById('veiculoLanc').innerHTML = '<option value="">Selecione o fornecedor primeiro</option>'; document.getElementById('radioQtde').checked = true; alternarTipoLancamento(); } function imprimirComprovante(dados) { const html = `<html><head><title>Comprovante de Entrega</title><style>@page{size:A4;margin:10mm}body{font-family:Arial,sans-serif;margin:0;padding:20px;zoom:0.8}.voucher{border:2px solid #2d5016;padding:20px;margin-bottom:10px;page-break-inside:avoid}.voucher-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;border-bottom:2px solid #2d5016;padding-bottom:15px}.logo{height:40px}.voucher-title{font-size:16px;font-weight:bold;color:#2d5016;text-align:center;flex-grow:1}.voucher-via{background:#2d5016;color:white;padding:5px 15px;border-radius:5px;font-size:12px}.voucher-info{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:15px;font-size:13px}.voucher-field{margin-bottom:8px}.voucher-field strong{color:#2d5016}.voucher-signature{margin-top:50px;display:flex;justify-content:space-between}.signature-line{width:200px;text-align:center;font-size:12px}.signature-line-top{border-top:1px solid #333;padding-top:5px}.signature-date{margin-top:15px;text-align:center}.voucher-separator{border-top:2px dashed #999;margin:20px 0;text-align:center;position:relative}.voucher-separator::before{content:'✂ CORTAR AQUI';background:white;padding:0 10px;position:relative;top:-10px;color:#999;font-size:11px}@media print{.voucher{page-break-inside:avoid}}</style></head><body>${gerarVoucherHTML(dados,'FINANCEIRO')}<div class="voucher-separator"></div>${gerarVoucherHTML(dados,'CONTABILIDADE')}<div class="voucher-separator"></div>${gerarVoucherHTML(dados,'ALMOXARIFADO')}</body></html>`; const novaJanela = window.open('', '_blank'); novaJanela.document.write(html); novaJanela.document.close(); setTimeout(function() { novaJanela.print(); }, 500); } function gerarVoucherHTML(dados, via) { let infoQuantidade = ''; let tituloComprovante = 'COMPROVANTE DE ENTREGA DE LENHA'; // Alterar título conforme o produto if (dados.produto && dados.produto.toLowerCase().includes('casca de arroz')) { tituloComprovante = 'COMPROVANTE DE ENTREGA DE CASCA DE ARROZ'; } else if (dados.produto && dados.produto.toLowerCase() !== 'lenha') { tituloComprovante = 'COMPROVANTE DE ENTREGA DE ' + dados.produto.toUpperCase(); } if (dados.tipoLancamento === 'Peso (Kg)') { const pesoFormatado = parseFloat(dados.peso).toFixed(1).replace('.', ','); const valorUnitFormatado = parseFloat(dados.valorUnitKg).toFixed(2).replace('.', ','); const pedidoFormatado = formatarPedido(dados.pedido); infoQuantidade = ` <div class="voucher-field"><strong>Pedido:</strong> ${pedidoFormatado}</div> <div class="voucher-field"><strong>Peso:</strong> ${pesoFormatado} Kg</div> <div class="voucher-field"><strong>Valor Unit. (Kg):</strong> R$ ${valorUnitFormatado}</div> <div class="voucher-field"><strong>Local de Entrega:</strong> ${dados.localEntrega}</div> <div class="voucher-field"><strong>Observação:</strong> ${dados.observacao || '-'}</div> `; } else { infoQuantidade = ` <div class="voucher-field"><strong>Código:</strong> ${dados.codigo}</div> <div class="voucher-field"><strong>Quantidade:</strong> ${dados.quantidade} m³</div> <div class="voucher-field"><strong>Local de Entrega:</strong> ${dados.localEntrega}</div> ${dados.observacao ? `<div class="voucher-field"><strong>Observação:</strong> ${dados.observacao}</div>` : ''} `; } const valorTotalFormatado = parseFloat(dados.valorTotal).toFixed(2).replace('.', ','); return `<div class="voucher"><div class="voucher-header"><img src="${window._logoUrl||''}" alt="Logo" class="logo"><div class="voucher-title">${tituloComprovante}</div><div class="voucher-via">${via}</div></div><div class="voucher-info"><div class="voucher-field"><strong>Empresa:</strong> ${dados.empresa}</div><div class="voucher-field"><strong>Data:</strong> ${formatarData(dados.data)}</div><div class="voucher-field"><strong>Hora:</strong> ${dados.hora}</div><div class="voucher-field"><strong>Fornecedor:</strong> ${dados.fornecedor}</div><div class="voucher-field"><strong>CPF:</strong> ${dados.cpf}</div><div class="voucher-field"><strong>Veículo:</strong> ${dados.veiculo}</div><div class="voucher-field"><strong>Placa:</strong> ${dados.placa}</div><div class="voucher-field"><strong>Produto:</strong> ${dados.produto}</div>${infoQuantidade}<div class="voucher-field"><strong>Valor Total (R$):</strong> R$ ${valorTotalFormatado}</div></div><div class="voucher-signature"><div class="signature-line"><div class="signature-date">Data: ______/______/_________</div></div><div class="signature-line"><div class="signature-line-top">Assinatura</div></div></div></div>`; } function imprimirLancamento(codigo) { const btn = event.target.closest('button'); const originalDisabled = btn.disabled; btn.disabled = true; const lancamento = lancamentosCache.find(l => l.codigo === codigo); if (!lancamento) { alert('Lançamento não encontrado! Código: ' + codigo); console.error('Cache:', lancamentosCache); btn.disabled = originalDisabled; return; } // Criar objeto completo para impressão const dadosImpressao = { codigo: lancamento.codigo, empresa: lancamento.empresa, data: lancamento.data, hora: lancamento.hora, fornecedor: lancamento.fornecedor, cpf: lancamento.cpf, veiculo: lancamento.veiculo, placa: lancamento.placa, produto: lancamento.produto, quantidade: lancamento.quantidade, valorTotal: lancamento.valorTotal, localEntrega: lancamento.localEntrega, observacao: lancamento.observacao, tipoLancamento: lancamento.tipoLancamento, peso: lancamento.peso, valorUnitKg: lancamento.valorUnitKg, pedido: lancamento.pedido }; imprimirComprovante(dadosImpressao); setTimeout(() => { btn.disabled = originalDisabled; }, 1000); } function carregarDadosFinanceiro() { if (financeiroCarregado) { return; } carregarFornecedores(); google.script.run.withSuccessHandler(function(locais) { const selectLocal = document.getElementById('filtroLocal'); selectLocal.innerHTML = '<option value="">Todos</option>'; locais.forEach(l => { selectLocal.innerHTML += `<option value="${l}">${l}</option>`; }); }).listarLocais(); setTimeout(function() { aplicarFiltrosFinanceiro(); financeiroCarregado = true; }, 500); } function aplicarFiltrosFinanceiro() { // Desabilitar botões durante processamento const btnPesquisar = document.querySelector('.filter-section button.btn-primary'); const btnLimpar = document.querySelector('.filter-section button.btn-secondary'); const btnImprimir = document.querySelector('.btn-imprimir-tabela'); if (btnPesquisar) btnPesquisar.disabled = true; if (btnLimpar) btnLimpar.disabled = true; if (btnImprimir) btnImprimir.disabled = true; // Mostrar "Carregando Dados" const tbody = document.getElementById('tabelaFinanceiro'); tbody.innerHTML = '<tr><td colspan="16" class="loading">Carregando dados...</td></tr>'; const filtros = { fornecedor: document.getElementById('filtroFornecedor').value || '', codigo: document.getElementById('filtroCodigo').value || '', pedido: document.getElementById('filtroPedido').value || '', numeroNF: document.getElementById('filtroNumeroNF').value || '', local: document.getElementById('filtroLocal').value || '', statusNF: document.getElementById('filtroStatusNF').value || '', dataInicio: document.getElementById('filtroDataInicio').value || '', dataFim: document.getElementById('filtroDataFim').value || '', limite: parseInt(document.getElementById('resultadosPorPagina').value) || 15 }; google.script.run.withSuccessHandler(function (respostaJson) { try { const resposta = JSON.parse(respostaJson); if (resposta.sucesso) { lancamentosCache = resposta.lancamentos || []; renderizarTabelaFinanceiro(resposta.lancamentos); } else { mostrarMensagem('errorLancamento', resposta.mensagem || 'Erro ao buscar lançamentos'); } } catch (e) { console.error('Erro ao processar resposta:', e); } // Reabilitar botões if (btnPesquisar) btnPesquisar.disabled = false; if (btnLimpar) btnLimpar.disabled = false; if (btnImprimir) btnImprimir.disabled = false; }).withFailureHandler(function (erro) { console.error('Erro na execução do Apps Script:', erro); // Reabilitar botões em caso de erro if (btnPesquisar) btnPesquisar.disabled = false; if (btnLimpar) btnLimpar.disabled = false; if (btnImprimir) btnImprimir.disabled = false; }).buscarLancamentosFinanceiro(filtros); } function limparFiltrosFinanceiro() { document.getElementById('filtroFornecedor').value = ''; document.getElementById('filtroCodigo').value = ''; document.getElementById('filtroPedido').value = ''; document.getElementById('filtroNumeroNF').value = ''; // 🆕 limpar Nº NF document.getElementById('filtroLocal').value = ''; document.getElementById('filtroStatusNF').value = ''; document.getElementById('filtroDataInicio').value = ''; document.getElementById('filtroDataFim').value = ''; document.getElementById('resultadosPorPagina').value = '10'; aplicarFiltrosFinanceiro(); } function calcularStatusNF(lanc) { const temNF = lanc.numeroNF && lanc.numeroNF !== ''; const temPagamento = lanc.statusPagamento === 'PAGO'; if (!temNF && !temPagamento) return 'EM ABERTO'; if (!temNF && temPagamento) return 'EMITIR NF'; if (temNF && !temPagamento) return 'A PAGAR'; if (temNF && temPagamento) return 'FINALIZADO'; return 'EM ABERTO'; } function renderizarTabelaFinanceiro(lancamentos) { const tbody = document.getElementById('tabelaFinanceiro'); tbody.innerHTML = ''; if (!lancamentos || lancamentos.length === 0) { tbody.innerHTML = '<tr><td colspan="17" style="text-align:center;">Nenhum lançamento encontrado.</td></tr>'; return; } lancamentos.forEach(lanc => { const tr = document.createElement('tr'); // Calcular o status NF corretamente const statusNF = calcularStatusNF(lanc); let statusClass = ''; if (statusNF === 'FINALIZADO') statusClass = 'status-pago'; else if (statusNF === 'A PAGAR') statusClass = 'status-a-pagar'; else if (statusNF === 'EMITIR NF') statusClass = 'status-emitir-nf'; else statusClass = 'status-aberto'; // Ícones dos botões const iconePagamento = lanc.statusPagamento === 'PAGO' ? '✅' : '💰'; const titlePagamento = lanc.statusPagamento === 'PAGO' ? 'Ver pagamento' : 'Registrar pagamento'; const temNF = lanc.numeroNF && lanc.numeroNF !== ''; const iconeNF = temNF ? '☑️' : '📜'; const titleNF = temNF ? 'Ver NF' : 'Registrar NF'; // Formatar quantidade e peso const displayQtdM3 = lanc.tipoLancamento === 'Qtde (m3)' && lanc.quantidade > 0 ? Math.round(parseFloat(lanc.quantidade)) : '-'; const displayPesoKg = lanc.tipoLancamento === 'Peso (Kg)' && lanc.peso > 0 ? formatarNumero(lanc.peso) : '-'; // Formatar NF com separador de milhar const nfFormatado = lanc.numeroNF ? formatarNumero(lanc.numeroNF) : '-'; // Formatar Pedido - 6 dígitos com separador de milhar let pedidoFormatado = '-'; if (lanc.pedido) { const pedidoLimpo = lanc.pedido.toString().replace(/\D/g, ''); if (/^[A-Za-z]/.test(lanc.pedido.toString())) { // Se começar com letra, manter formato original pedidoFormatado = lanc.pedido; } else if (pedidoLimpo) { // Se for apenas números, formatar com 6 dígitos e separador de milhar const pedidoNum = parseInt(pedidoLimpo, 10); if (!isNaN(pedidoNum)) { const pedidoPadded = pedidoNum.toString().padStart(6, '0'); pedidoFormatado = pedidoPadded.slice(0, 3) + '.' + pedidoPadded.slice(3); } } } tr.innerHTML = ` <td style="white-space: nowrap;"> <button class="btn-excluir" title="Excluir" onclick="confirmarExclusao('${lanc.codigo}')">🗑️</button> <button class="btn-imprimir" title="Imprimir" onclick="imprimirLancamento('${lanc.codigo}')">🖨️</button> <button class="btn-nota-fiscal" title="${titleNF}" onclick="abrirModalNotaFiscal('${lanc.codigo}')">${iconeNF}</button> <button class="btn-pagar" title="${titlePagamento}" onclick="abrirModalPagamento('${lanc.codigo}')">${iconePagamento}</button> </td> <td><span class="status-badge ${statusClass}">${statusNF}</span></td> <td class="group-border-left">${lanc.numeroPagamento || '-'}</td> <td>${nfFormatado}</td> <td>${pedidoFormatado}</td> <td class="group-border-left">${formatarData(lanc.data)}</td> <td>${lanc.hora || '-'}</td> <td>${lanc.localEntrega || '-'}</td> <td>${lanc.codigo || '-'}</td> <td class="group-border-left">${lanc.fornecedor || '-'}</td> <td>${lanc.produto || '-'}</td> <td>${lanc.veiculo || '-'}</td> <td>${lanc.placa || '-'}</td> <td class="group-border-left">${displayQtdM3}</td> <td>${displayPesoKg}</td> <td class="group-border-left">${formatarValorReal(lanc.valorTotal || 0)}</td> <td style="font-size: 10px; max-width: 100px; word-wrap: break-word;">${lanc.observacao || '-'}</td> `; // Impedir conflito de clique entre linha e botões tr.addEventListener('click', function (e) { if (e.target.closest('button')) { e.stopPropagation(); return; } document.querySelectorAll('#tabelaFinanceiro tr').forEach(row => { row.classList.remove('selected'); }); this.classList.add('selected'); }); tbody.appendChild(tr); }); // Calcular totais let totalLancamentos = lancamentos.length; let totalQtdM3 = 0; let totalPesoKg = 0; let totalValor = 0; lancamentos.forEach(lanc => { if (lanc.tipoLancamento === 'Qtde (m3)' && lanc.quantidade > 0) { totalQtdM3 += parseFloat(lanc.quantidade); } if (lanc.tipoLancamento === 'Peso (Kg)' && lanc.peso > 0) { totalPesoKg += parseFloat(lanc.peso); } totalValor += parseFloat(lanc.valorTotal) || 0; }); const trTotal = document.createElement('tr'); trTotal.className = 'linha-totais'; trTotal.innerHTML = ` <td colspan="1" style="color: white;"></td> <td colspan="1" style="color: white;"></td> <td colspan="1" class="group-border-left" style="color: white;"></td> <td colspan="1" style="color: white;"></td> <td colspan="1" style="color: white;"></td> <td colspan="1" class="group-border-left" style="color: white;"></td> <td colspan="1" style="color: white;"></td> <td colspan="1" style="color: white;"></td> <td colspan="1" style="color: white;"></td> <td colspan="1" class="group-border-left" style="color: white;"></td> <td colspan="1" style="color: white;"></td> <td colspan="1" style="color: white;"></td> <td colspan="1" style="text-align: right; font-weight: bold; color: white;">TOTAIS: ${totalLancamentos}</td> <td class="group-border-left" style="font-weight: bold; color: white; text-align: center;">${totalQtdM3 > 0 ? Math.round(totalQtdM3) : '-'}</td> <td style="font-weight: bold; color: white; text-align: center;">${totalPesoKg > 0 ? formatarNumero(totalPesoKg) : '-'}</td> <td class="group-border-left" style="font-weight: bold; color: white; text-align: center;">${formatarValorReal(totalValor)}</td> <td style="color: white;"></td> `; tbody.appendChild(trTotal); } function atualizarNumeroPagamento() { const tipoPagamento = document.getElementById('tipoPagamento').value; const numeroPagamento = document.getElementById('numeroPagamento'); if (tipoPagamento === 'Transferência') { numeroPagamento.value = 'TED'; numeroPagamento.readOnly = true; } else { if (numeroPagamento.value === 'TED') { numeroPagamento.value = ''; } numeroPagamento.readOnly = false; numeroPagamento.placeholder = 'Digite o número do cheque'; } } function abrirModalPagamento(codigo) { const lancamento = lancamentosCache.find(l => l.codigo === codigo); if (!lancamento) { alert('Lançamento não encontrado no cache!'); return; } document.getElementById('codigoPagamento').value = codigo; // Se já existe pagamento, preencher os campos if (lancamento.statusPagamento === 'PAGO') { document.getElementById('dataPagamento').value = lancamento.dataPagamento ? new Date(lancamento.dataPagamento).toISOString().split('T')[0] : ''; document.getElementById('tipoPagamento').value = lancamento.tipoPagamento || ''; document.getElementById('numeroPagamento').value = lancamento.numeroPagamento || ''; document.getElementById('observacaoPagamento').value = lancamento.obsPagamento || ''; // Modo visualização/edição const btnSubmit = document.querySelector('#formPagamento button[type="submit"]'); btnSubmit.textContent = 'Atualizar Pagamento'; } else { // Limpar campos para novo pagamento document.getElementById('numeroPagamento').value = ''; document.getElementById('observacaoPagamento').value = ''; document.getElementById('tipoPagamento').value = ''; const hoje = new Date().toISOString().split('T')[0]; document.getElementById('dataPagamento').value = hoje; const btnSubmit = document.querySelector('#formPagamento button[type="submit"]'); btnSubmit.textContent = 'Confirmar Pagamento'; } document.getElementById('numeroPagamento').readOnly = false; document.getElementById('modalPagamento').classList.add('active'); } function fecharModalPagamento() { document.getElementById('modalPagamento').classList.remove('active'); document.getElementById('formPagamento').reset(); const hoje = new Date().toISOString().split('T')[0]; document.getElementById('dataPagamento').value = hoje; document.getElementById('dataPagamento').disabled = false; document.getElementById('tipoPagamento').disabled = false; document.getElementById('numeroPagamento').disabled = false; document.getElementById('numeroPagamento').readOnly = false; document.getElementById('observacaoPagamento').disabled = false; const btnSubmit = document.querySelector('#formPagamento button[type="submit"]'); btnSubmit.textContent = 'Confirmar'; btnSubmit.style.display = 'inline-block'; } document.getElementById('formPagamento').addEventListener('submit', function(e) { e.preventDefault(); const btnConfirmar = e.target.querySelector('button[type="submit"]'); btnConfirmar.disabled = true; const codigo = document.getElementById('codigoPagamento').value; const dataPagamento = document.getElementById('dataPagamento').value; const tipoPagamento = document.getElementById('tipoPagamento').value; const numeroPagamento = document.getElementById('numeroPagamento').value; const observacaoPagamento = document.getElementById('observacaoPagamento').value; google.script.run.withSuccessHandler(function(msg) { alert(msg); fecharModalPagamento(); financeiroCarregado = false; aplicarFiltrosFinanceiro(); btnConfirmar.disabled = false; }).withFailureHandler(function(error) { alert('Erro ao registrar pagamento: ' + error); btnConfirmar.disabled = false; }).registrarPagamento(codigo, dataPagamento, tipoPagamento, numeroPagamento, observacaoPagamento); }); function abrirModalNotaFiscal(codigo) { const lancamento = lancamentosCache.find(l => l.codigo === codigo); if (!lancamento) { alert('Lançamento não encontrado no cache!'); return; } document.getElementById('codigoNotaFiscal').value = codigo; // Se já existe NF, preencher os campos if (lancamento.numeroNF && lancamento.numeroNF !== '') { document.getElementById('dataEmissaoNF').value = lancamento.dataEmissaoNF ? new Date(lancamento.dataEmissaoNF).toISOString().split('T')[0] : ''; document.getElementById('numeroNF').value = lancamento.numeroNF || ''; document.getElementById('observacaoNF').value = lancamento.obsNF || ''; // Modo visualização/edição const btnSubmit = document.querySelector('#formNotaFiscal button[type="submit"]'); btnSubmit.textContent = 'Atualizar NF'; } else { // Limpar campos para nova NF const hoje = new Date().toISOString().split('T')[0]; document.getElementById('dataEmissaoNF').value = hoje; document.getElementById('numeroNF').value = ''; document.getElementById('observacaoNF').value = ''; const btnSubmit = document.querySelector('#formNotaFiscal button[type="submit"]'); btnSubmit.textContent = 'Confirmar NF'; } document.getElementById('modalNotaFiscal').classList.add('active'); } function fecharModalNotaFiscal() { document.getElementById('modalNotaFiscal').classList.remove('active'); document.getElementById('formNotaFiscal').reset(); const hoje = new Date().toISOString().split('T')[0]; document.getElementById('dataEmissaoNF').value = hoje; document.getElementById('dataEmissaoNF').disabled = false; document.getElementById('numeroNF').disabled = false; document.getElementById('observacaoNF').disabled = false; const btnSubmit = document.querySelector('#formNotaFiscal button[type="submit"]'); btnSubmit.textContent = 'Confirmar'; btnSubmit.style.display = 'inline-block'; } document.getElementById('formNotaFiscal').addEventListener('submit', function(e) { e.preventDefault(); const btnConfirmar = e.target.querySelector('button[type="submit"]'); btnConfirmar.disabled = true; const codigo = document.getElementById('codigoNotaFiscal').value; const dataEmissao = document.getElementById('dataEmissaoNF').value; const numeroNF = document.getElementById('numeroNF').value; const observacaoNF = document.getElementById('observacaoNF').value; google.script.run.withSuccessHandler(function(msg) { alert(msg); fecharModalNotaFiscal(); financeiroCarregado = false; aplicarFiltrosFinanceiro(); btnConfirmar.disabled = false; }).withFailureHandler(function(error) { alert('Erro ao registrar nota fiscal: ' + error); btnConfirmar.disabled = false; }).registrarNotaFiscal(codigo, dataEmissao, numeroNF, observacaoNF); }); let charts = {}; function visualizarNotaFiscal(codigo) { const lancamento = lancamentosCache.find(l => l.codigo === codigo); if (!lancamento) { alert('Lançamento não encontrado!'); return; } document.getElementById('codigoNotaFiscal').value = codigo; document.getElementById('dataEmissaoNF').value = lancamento.dataEmissaoNF ? new Date(lancamento.dataEmissaoNF).toISOString().split('T')[0] : ''; document.getElementById('numeroNF').value = lancamento.numeroNF || ''; document.getElementById('observacaoNF').value = lancamento.obsNF || ''; document.getElementById('dataEmissaoNF').disabled = false; document.getElementById('numeroNF').disabled = false; document.getElementById('observacaoNF').disabled = false; const btnSubmit = document.querySelector('#formNotaFiscal button[type="submit"]'); btnSubmit.textContent = 'Atualizar'; btnSubmit.style.display = 'inline-block'; document.getElementById('modalNotaFiscal').classList.add('active'); } function visualizarPagamento(codigo) { const lancamento = lancamentosCache.find(l => l.codigo === codigo); if (!lancamento) { alert('Lançamento não encontrado!'); return; } document.getElementById('codigoPagamento').value = codigo; document.getElementById('dataPagamento').value = lancamento.dataPagamento ? new Date(lancamento.dataPagamento).toISOString().split('T')[0] : ''; document.getElementById('tipoPagamento').value = lancamento.tipoPagamento || ''; document.getElementById('numeroPagamento').value = lancamento.numeroPagamento || ''; document.getElementById('observacaoPagamento').value = lancamento.obsPagamento || ''; document.getElementById('dataPagamento').disabled = false; document.getElementById('tipoPagamento').disabled = false; document.getElementById('numeroPagamento').disabled = false; document.getElementById('observacaoPagamento').disabled = false; const btnSubmit = document.querySelector('#formPagamento button[type="submit"]'); btnSubmit.textContent = 'Atualizar'; btnSubmit.style.display = 'inline-block'; document.getElementById('modalPagamento').classList.add('active'); } function carregarDadosDashboard() { google.script.run.withSuccessHandler(function(locais) { const selectLocal = document.getElementById('dashLocal'); selectLocal.innerHTML = '<option value="">Todos</option>'; locais.forEach(l => { selectLocal.innerHTML += `<option value="${l}">${l}</option>`; }); }).listarLocais(); atualizarDashboard(); } function atualizarDashboard() { const btnAtualizar = document.querySelector('.filter-section button.btn-primary'); const btnLimpar = document.querySelector('.filter-section button.btn-secondary'); const btnImprimir = document.querySelector('.filter-section .btn-imprimir-tabela'); // Desabilitar botões if (btnAtualizar) btnAtualizar.disabled = true; if (btnLimpar) btnLimpar.disabled = true; if (btnImprimir) btnImprimir.disabled = true; const filtros = { fornecedor: document.getElementById('dashFornecedor').value || '', codigo: document.getElementById('dashCodigo').value || '', pedido: document.getElementById('dashPedido').value || '', numeroNF: document.getElementById('dashNumeroNF').value || '', local: document.getElementById('dashLocal').value || '', statusNF: document.getElementById('dashStatusNF').value || '', dataInicio: document.getElementById('dashDataInicio').value || '', dataFim: document.getElementById('dashDataFim').value || '' }; google.script.run.withSuccessHandler(function (dados) { renderizarDashboard(dados); // Reabilitar botões if (btnAtualizar) btnAtualizar.disabled = false; if (btnLimpar) btnLimpar.disabled = false; if (btnImprimir) btnImprimir.disabled = false; }).withFailureHandler(function(error) { console.error('Erro ao carregar dashboard:', error); alert('Erro ao carregar dados do dashboard'); // Reabilitar botões mesmo em caso de erro if (btnAtualizar) btnAtualizar.disabled = false; if (btnLimpar) btnLimpar.disabled = false; if (btnImprimir) btnImprimir.disabled = false; }).obterDadosDashboard(filtros); } function limparFiltrosDashboard() { const btnAtualizar = document.querySelector('.filter-section button.btn-primary'); const btnLimpar = document.querySelector('.filter-section button.btn-secondary'); const btnImprimir = document.querySelector('.filter-section .btn-imprimir-tabela'); // Desabilitar botões if (btnAtualizar) btnAtualizar.disabled = true; if (btnLimpar) btnLimpar.disabled = true; if (btnImprimir) btnImprimir.disabled = true; document.getElementById('dashFornecedor').value = ''; document.getElementById('dashCodigo').value = ''; document.getElementById('dashPedido').value = ''; document.getElementById('dashNumeroNF').value = ''; document.getElementById('dashLocal').value = ''; document.getElementById('dashStatusNF').value = ''; document.getElementById('dashDataInicio').value = ''; document.getElementById('dashDataFim').value = ''; atualizarDashboard(); } function renderizarDashboard(dados) { document.getElementById('qtdeLenha').textContent = Math.round(dados.qtdeLenha).toLocaleString('pt-BR'); document.getElementById('qtdeCascaArroz').textContent = Math.round(dados.qtdeCascaArroz).toLocaleString('pt-BR'); document.getElementById('valorLenha').textContent = dados.valorLenha.toLocaleString('pt-BR', {minimumFractionDigits: 2, maximumFractionDigits: 2}); document.getElementById('valorCascaArroz').textContent = dados.valorCascaArroz.toLocaleString('pt-BR', {minimumFractionDigits: 2, maximumFractionDigits: 2}); document.getElementById('totalPago').textContent = dados.totalPago.toLocaleString('pt-BR', {minimumFractionDigits: 2}); document.getElementById('totalAPagar').textContent = dados.totalAPagar.toLocaleString('pt-BR', {minimumFractionDigits: 2}); Object.values(charts).forEach(chart => chart.destroy()); charts = {}; // Gráfico: Quantidade por Local const ctxLocal = document.getElementById('chartLocal').getContext('2d'); charts.chartLocal = new Chart(ctxLocal, { type: 'pie', data: { labels: Object.keys(dados.quantidadePorLocal), datasets: [{ data: Object.values(dados.quantidadePorLocal), backgroundColor: ['#4a7c2a', '#6d4c41', '#2d5016', '#8d6e63', '#558b2f'] }] }, options: { responsive: true, maintainAspectRatio: true, plugins: { legend: { labels: { color: '#e8e6e3' } }, datalabels: { color: '#fff', font: { weight: 'bold', size: 14 }, formatter: (value) => value.toFixed(0) } } } }); // Gráfico: Quantidade por Produto const ctxProduto = document.getElementById('chartProduto').getContext('2d'); charts.chartProduto = new Chart(ctxProduto, { type: 'bar', data: { labels: Object.keys(dados.qtdePorProduto), datasets: [{ label: 'Quantidade (Kg)', data: Object.values(dados.qtdePorProduto), backgroundColor: ['#4a7c2a', '#6d4c41', getCssVar('--cor-primaria')], borderColor: [getCssVar('--cor-primaria'), '#5d4037', '#1a1410'], borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: true, plugins: { legend: { display: false }, datalabels: { color: '#fff', font: { weight: 'bold', size: 12 }, anchor: 'end', align: 'top', formatter: (value) => value.toFixed(0) } }, scales: { x: { ticks: { color: '#e8e6e3' } }, y: { ticks: { color: '#e8e6e3' } } } } }); // Gráfico: Quantidade por Empresa por Mês (últimos 6 meses) - COM CORES CORRIGIDAS var meses = dados.mesesParaGrafico || []; var empresas = Object.keys(dados.compraPorEmpresa); var produtos = ['Lenha', 'Casca de Arroz']; var datasets = []; var cores = { // Cores geradas dinamicamente por índice de empresa usando variáveis CSS }; // Preenchimento dinâmico: usa variáveis CSS para as cores dos gráficos var _paleta = [ [getCssVar('--cor-secundaria'), getCssVar('--cor-terciaria')], [getCssVar('--cor-primaria'), shadeColor(getCssVar('--cor-terciaria'), 20)], ['#90a4ae', '#b0bec5'], ['#a5d6a7', '#c8e6c9'] ]; empresas.forEach(function(emp, idx) { var pal = _paleta[idx % _paleta.length]; coresEmpresa[emp] = { 'Lenha': pal[0], 'Casca de Arroz': pal[1] }; // Fallback para outros produtos Object.keys(produtos).forEach(function(prod) { if (!coresEmpresa[emp][prod]) coresEmpresa[emp][prod] = pal[idx % pal.length]; }); }); empresas.forEach(function(empresa) { produtos.forEach(function(produto) { var dadosMes = []; meses.forEach(function(mes) { var valor = 0; if (dados.compraPorEmpresaMes[mes] && dados.compraPorEmpresaMes[mes][empresa] && dados.compraPorEmpresaMes[mes][empresa][produto]) { valor = dados.compraPorEmpresaMes[mes][empresa][produto]; } dadosMes.push(valor); }); datasets.push({ label: empresa + ' - ' + produto, data: dadosMes, backgroundColor: cores[empresa][produto], borderColor: cores[empresa][produto], borderWidth: 1 }); }); }); const ctxEmpresaQtd = document.getElementById('chartEmpresaQtd').getContext('2d'); charts.chartEmpresaQtd = new Chart(ctxEmpresaQtd, { type: 'bar', data: { labels: meses, datasets: datasets }, options: { responsive: true, maintainAspectRatio: true, plugins: { legend: { display: true, labels: { color: '#e8e6e3', font: { size: 10 } } }, datalabels: { color: '#fff', font: { weight: 'bold', size: 9 }, anchor: 'center', align: 'center', formatter: (value) => value > 0 ? value.toFixed(0) : '' } }, scales: { x: { stacked: false, ticks: { color: '#e8e6e3', font: { size: 10 } } }, y: { stacked: false, ticks: { color: '#e8e6e3' } } } } }); // Gráfico: Valor por Empresa por Mês (últimos 6 meses) - COM CORES CORRIGIDAS var datasetsValor = []; empresas.forEach(function(empresa) { produtos.forEach(function(produto) { var dadosMes = []; meses.forEach(function(mes) { var valor = 0; if (dados.valorPorEmpresaMes[mes] && dados.valorPorEmpresaMes[mes][empresa] && dados.valorPorEmpresaMes[mes][empresa][produto]) { valor = dados.valorPorEmpresaMes[mes][empresa][produto]; } dadosMes.push(valor); }); datasetsValor.push({ label: empresa + ' - ' + produto, data: dadosMes, backgroundColor: cores[empresa][produto], borderColor: cores[empresa][produto], borderWidth: 1 }); }); }); const ctxEmpresaValor = document.getElementById('chartEmpresaValor').getContext('2d'); charts.chartEmpresaValor = new Chart(ctxEmpresaValor, { type: 'bar', data: { labels: meses, datasets: datasetsValor }, options: { responsive: true, maintainAspectRatio: true, plugins: { legend: { display: true, labels: { color: '#e8e6e3', font: { size: 10 } } }, datalabels: { color: '#fff', font: { weight: 'bold', size: 9 }, anchor: 'center', align: 'center', formatter: (value) => value > 0 ? 'R$ ' + value.toFixed(0) : '' } }, scales: { x: { stacked: false, ticks: { color: '#e8e6e3', font: { size: 10 } } }, y: { stacked: false, ticks: { color: '#e8e6e3' } } } } }); // Mantém os gráficos de Fornecedor const ctxFornecedorQtd = document.getElementById('chartFornecedorQtd').getContext('2d'); charts.chartFornecedorQtd = new Chart(ctxFornecedorQtd, { type: 'bar', data: { labels: Object.keys(dados.quantidadePorFornecedor), datasets: [{ label: 'Quantidade (Kg)', data: Object.values(dados.quantidadePorFornecedor), backgroundColor: '#4a7c2a', borderColor: getCssVar('--cor-primaria'), borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: true, indexAxis: 'y', plugins: { legend: { display: false }, datalabels: { color: '#fff', font: { weight: 'bold', size: 11 }, anchor: 'end', align: 'right', formatter: (value) => value.toFixed(0) } }, scales: { x: { ticks: { color: '#e8e6e3' } }, y: { ticks: { color: '#e8e6e3', font: { size: 10 } } } } } }); const ctxFornecedorValor = document.getElementById('chartFornecedorValor').getContext('2d'); charts.chartFornecedorValor = new Chart(ctxFornecedorValor, { type: 'bar', data: { labels: Object.keys(dados.valorPorFornecedor), datasets: [{ label: 'Valor Total (R$)', data: Object.values(dados.valorPorFornecedor), backgroundColor: '#6d4c41', borderColor: '#5d4037', borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: true, indexAxis: 'y', plugins: { legend: { display: false }, datalabels: { color: '#fff', font: { weight: 'bold', size: 11 }, anchor: 'end', align: 'right', formatter: (value) => 'R$ ' + value.toFixed(0) } }, scales: { x: { ticks: { color: '#e8e6e3' } }, y: { ticks: { color: '#e8e6e3', font: { size: 10 } } } } } }); // Gráfico: Preço Médio LENHA (últimos 12 meses) const mesesOrdenadosLenha = Object.keys(dados.precoMedioPorMesLenha).sort((a, b) => { const [mesA, anoA] = a.split('/').map(Number); const [mesB, anoB] = b.split('/').map(Number); return (anoA * 12 + mesA) - (anoB * 12 + mesB); }).slice(-12); const ctxPrecoLenha = document.getElementById('chartPrecoLenha').getContext('2d'); charts.chartPrecoLenha = new Chart(ctxPrecoLenha, { type: 'line', data: { labels: mesesOrdenadosLenha, datasets: [{ label: 'Preço Médio Lenha (R$/Kg)', data: mesesOrdenadosLenha.map(mes => dados.precoMedioPorMesLenha[mes]), borderColor: '#4a7c2a', backgroundColor: 'rgba(74, 124, 42, 0.1)', fill: true, tension: 0.4 }] }, options: { responsive: true, maintainAspectRatio: true, plugins: { legend: { labels: { color: '#e8e6e3' } }, datalabels: { color: '#4a7c2a', font: { weight: 'bold', size: 11 }, backgroundColor: 'rgba(255, 255, 255, 0.8)', borderRadius: 4, padding: 4, formatter: (value) => value ? 'R$ ' + value.toFixed(2) : '' } }, scales: { x: { ticks: { color: '#e8e6e3' } }, y: { ticks: { color: '#e8e6e3' } } } } }); // Gráfico: Preço Médio CASCA DE ARROZ (últimos 12 meses) const mesesOrdenadosCasca = Object.keys(dados.precoMedioPorMesCasca).sort((a, b) => { const [mesA, anoA] = a.split('/').map(Number); const [mesB, anoB] = b.split('/').map(Number); return (anoA * 12 + mesA) - (anoB * 12 + mesB); }).slice(-12); const ctxPrecoCasca = document.getElementById('chartPrecoCasca').getContext('2d'); charts.chartPrecoCasca = new Chart(ctxPrecoCasca, { type: 'line', data: { labels: mesesOrdenadosCasca, datasets: [{ label: 'Preço Médio Casca de Arroz (R$/Kg)', data: mesesOrdenadosCasca.map(mes => dados.precoMedioPorMesCasca[mes]), borderColor: '#8d6e63', backgroundColor: 'rgba(141, 110, 99, 0.1)', fill: true, tension: 0.4 }] }, options: { responsive: true, maintainAspectRatio: true, plugins: { legend: { labels: { color: '#e8e6e3' } }, datalabels: { color: '#8d6e63', font: { weight: 'bold', size: 11 }, backgroundColor: 'rgba(255, 255, 255, 0.8)', borderRadius: 4, padding: 4, formatter: (value) => value ? 'R$ ' + value.toFixed(2) : '' } }, scales: { x: { ticks: { color: '#e8e6e3' } }, y: { ticks: { color: '#e8e6e3' } } } } }); } function mostrarMensagem(elementId, mensagem) { const el = document.getElementById(elementId); el.textContent = mensagem; el.style.display = 'block'; setTimeout(() => { el.style.display = 'none'; }, 5000); } function formatarData(data) { if (!data) return ''; const d = new Date(data); const dia = String(d.getUTCDate()).padStart(2, '0'); const mes = String(d.getUTCMonth() + 1).padStart(2, '0'); const ano = d.getUTCFullYear(); return `${dia}/${mes}/${ano}`; } document.getElementById('cpfFornecedor').addEventListener('input', function(e) { let value = e.target.value.replace(/\D/g, ''); if (value.length <= 11) { value = value.replace(/(\d{3})(\d)/, '$1.$2'); value = value.replace(/(\d{3})(\d)/, '$1.$2'); value = value.replace(/(\d{3})(\d{1,2})$/, '$1-$2'); e.target.value = value; } }); document.getElementById('placaFornecedor').addEventListener('input', function(e) { let value = e.target.value.replace(/[^A-Za-z0-9]/g, '').toUpperCase(); if (value.length > 3) { value = value.substring(0, 3) + '-' + value.substring(3, 7); } e.target.value = value; }); let ordemAtual = { coluna: null, crescente: true }; function ordenarTabela(coluna) { const tbody = document.getElementById('tabelaFinanceiro'); const linhas = Array.from(tbody.getElementsByTagName('tr')).filter(tr => !tr.classList.contains('linha-totais')); if (ordemAtual.coluna === coluna) { ordemAtual.crescente = !ordemAtual.crescente; } else { ordemAtual.coluna = coluna; ordemAtual.crescente = true; } const indices = { 'status': 1, 'pagamento': 2, 'nf': 3, 'pedido': 4, 'data': 5, 'hora': 6, 'local': 7, 'codigo': 8, 'fornecedor': 9, 'veiculo': 10, 'placa': 11, 'quantidade': 12, 'peso': 13, 'valor': 14 }; const indice = indices[coluna]; linhas.sort((a, b) => { let valorA = a.cells[indice].textContent.trim(); let valorB = b.cells[indice].textContent.trim(); if (coluna === 'quantidade' || coluna === 'peso' || coluna === 'valor') { valorA = parseFloat(valorA.replace(/[^\d,.-]/g, '').replace(',', '.')) || 0; valorB = parseFloat(valorB.replace(/[^\d,.-]/g, '').replace(',', '.')) || 0; } if (coluna === 'data') { valorA = valorA.split('/').reverse().join(''); valorB = valorB.split('/').reverse().join(''); } if (valorA < valorB) return ordemAtual.crescente ? -1 : 1; if (valorA > valorB) return ordemAtual.crescente ? 1 : -1; return 0; }); const linhaTotal = tbody.querySelector('.linha-totais'); tbody.innerHTML = ''; linhas.forEach(linha => tbody.appendChild(linha)); if (linhaTotal) tbody.appendChild(linhaTotal); document.querySelectorAll('th').forEach(th => { th.classList.remove('sorted-asc', 'sorted-desc'); }); const thClicado = document.querySelector(`th[data-coluna="${coluna}"]`); if (thClicado) { thClicado.classList.add(ordemAtual.crescente ? 'sorted-asc' : 'sorted-desc'); } } function confirmarExclusao(codigo) { const btn = event.target.closest('button'); const originalDisabled = btn.disabled; btn.disabled = true; if (confirm('Tem certeza que deseja excluir o lançamento ' + codigo + '? Esta ação não pode ser desfeita.')) { google.script.run .withSuccessHandler(function(msg) { alert(msg); financeiroCarregado = false; aplicarFiltrosFinanceiro(); }) .withFailureHandler(function(error) { alert('Erro ao excluir: ' + error.message); btn.disabled = originalDisabled; }) .excluirLancamento(codigo); } else { btn.disabled = originalDisabled; } } function imprimirTabelaFinanceiro() { const btn = event.target; btn.disabled = true; if (!lancamentosCache || lancamentosCache.length === 0) { alert('Não há dados para imprimir. Por favor, realize uma pesquisa primeiro.'); btn.disabled = false; return; } const filtrosAtivos = obterFiltrosAtivos(); const htmlImpressao = gerarHTMLImpressaoFinanceiro(lancamentosCache, filtrosAtivos); const janelaImpressao = window.open('', '_blank'); janelaImpressao.document.write(htmlImpressao); janelaImpressao.document.close(); setTimeout(function() { janelaImpressao.focus(); janelaImpressao.print(); btn.disabled = false; }, 500); } function imprimirDashboard() { const btn = event.target.closest('button'); if (!btn) return; btn.disabled = true; // Verificar se os gráficos foram renderizados if (!charts.chartLocal || !charts.chartProduto) { alert('Aguarde o carregamento completo do dashboard antes de imprimir.'); btn.disabled = false; return; } try { const filtrosAtivos = obterFiltrosAtivosDashboard(); const htmlImpressao = gerarHTMLImpressaoDashboard(filtrosAtivos); const janelaImpressao = window.open('', '_blank'); if (!janelaImpressao) { alert('Pop-up bloqueado. Por favor, permita pop-ups para imprimir.'); btn.disabled = false; return; } janelaImpressao.document.write(htmlImpressao); janelaImpressao.document.close(); setTimeout(function() { janelaImpressao.focus(); janelaImpressao.print(); btn.disabled = false; }, 1000); } catch (error) { console.error('Erro ao imprimir:', error); alert('Erro ao gerar impressão. Veja o console para detalhes.'); btn.disabled = false; } } function obterFiltrosAtivos() { const filtros = []; const fornecedor = document.getElementById('filtroFornecedor').value; const codigo = document.getElementById('filtroCodigo').value; const pedido = document.getElementById('filtroPedido').value; const local = document.getElementById('filtroLocal').value; const statusNF = document.getElementById('filtroStatusNF').value; const dataInicio = document.getElementById('filtroDataInicio').value; const dataFim = document.getElementById('filtroDataFim').value; if (fornecedor) filtros.push(`Fornecedor: ${fornecedor}`); if (codigo) filtros.push(`Código: ${codigo}`); if (pedido) filtros.push(`Pedido: ${pedido}`); if (local) filtros.push(`Local: ${local}`); if (statusNF) filtros.push(`Status NF: ${statusNF}`); if (dataInicio && dataFim) filtros.push(`Período: ${formatarData(dataInicio)} a ${formatarData(dataFim)}`); return filtros.length > 0 ? filtros.join(' | ') : 'Todos os registros'; } function obterFiltrosAtivosDashboard() { const filtros = []; const fornecedor = document.getElementById('dashFornecedor').value; const codigo = document.getElementById('dashCodigo').value; const pedido = document.getElementById('dashPedido').value; const local = document.getElementById('dashLocal').value; const statusNF = document.getElementById('dashStatusNF').value; const dataInicio = document.getElementById('dashDataInicio').value; const dataFim = document.getElementById('dashDataFim').value; if (fornecedor) filtros.push(`Fornecedor: ${fornecedor}`); if (codigo) filtros.push(`Código: ${codigo}`); if (pedido) filtros.push(`Pedido: ${pedido}`); if (local) filtros.push(`Local: ${local}`); if (statusNF) filtros.push(`Status NF: ${statusNF}`); if (dataInicio && dataFim) filtros.push(`Período: ${formatarData(dataInicio)} a ${formatarData(dataFim)}`); return filtros.length > 0 ? filtros.join(' | ') : 'Todos os registros'; } function gerarHTMLImpressaoFinanceiro(lancamentos, filtrosAtivos) { const dataHoraImpressao = new Date().toLocaleString('pt-BR'); let totalQuantidade = 0; let totalPeso = 0; let totalValor = 0; let totalPago = 0; let totalAberto = 0; lancamentos.forEach(lanc => { totalQuantidade += parseFloat(lanc.quantidade) || 0; totalPeso += parseFloat(lanc.peso) || 0; totalValor += parseFloat(lanc.valorTotal) || 0; if (lanc.statusPagamento === 'PAGO') { totalPago += parseFloat(lanc.valorTotal) || 0; } else { totalAberto += parseFloat(lanc.valorTotal) || 0; } }); let linhasTabela = ''; lancamentos.forEach(lanc => { const status = calcularStatusNF(lanc); let statusClass = 'print-status-aberto'; if (status === 'FINALIZADO') statusClass = 'print-status-pago'; const nfFormatado = lanc.numeroNF ? formatarNumero(lanc.numeroNF) : '-'; // Formatar Pedido - 6 dígitos com separador de milhar let pedidoFormatado = '-'; if (lanc.pedido) { const pedidoLimpo = lanc.pedido.toString().replace(/\D/g, ''); if (/^[A-Za-z]/.test(lanc.pedido.toString())) { pedidoFormatado = lanc.pedido; } else if (pedidoLimpo) { const pedidoNum = parseInt(pedidoLimpo, 10); if (!isNaN(pedidoNum)) { const pedidoPadded = pedidoNum.toString().padStart(6, '0'); pedidoFormatado = pedidoPadded.slice(0, 3) + '.' + pedidoPadded.slice(3); } } } const displayQtdM3 = lanc.tipoLancamento === 'Qtde (m3)' && lanc.quantidade > 0 ? parseFloat(lanc.quantidade).toFixed(2) : '-'; const displayPesoKg = lanc.tipoLancamento === 'Peso (Kg)' && lanc.peso > 0 ? formatarNumero(lanc.peso) : '-'; linhasTabela += ` <tr> <td><span class="print-status-badge ${statusClass}">${status}</span></td> <td>${lanc.numeroPagamento || '-'}</td> <td>${nfFormatado}</td> <td>${pedidoFormatado}</td> <td>${formatarData(lanc.data)}</td> <td>${lanc.hora || '-'}</td> <td>${lanc.codigo || '-'}</td> <td>${lanc.fornecedor || '-'}</td> <td>${lanc.produto || '-'}</td> <td>${lanc.veiculo || '-'}</td> <td>${lanc.placa || '-'}</td> <td>${displayQtdM3}</td> <td>${displayPesoKg}</td> <td>${formatarValorReal(lanc.valorTotal || 0)}</td> <td>${lanc.localEntrega || '-'}</td> <td style="font-size: 7px; max-width: 80px; word-wrap: break-word;">${lanc.observacao || '-'}</td> </tr> `; }); linhasTabela += ` <tr style="background: #6d4c41; font-weight: bold; border-top: 2px solid #2d5016; color: white;"> <td colspan="1" style="color: white;"></td> <td colspan="1" style="color: white;"></td> <td colspan="1" style="color: white;"></td> <td colspan="1" style="color: white;"></td> <td colspan="1" style="color: white;"></td> <td colspan="1" style="color: white;"></td> <td colspan="1" style="color: white;"></td> <td colspan="1" style="color: white;"></td> <td colspan="1" style="color: white;"></td> <td colspan="1" style="color: white;"></td> <td colspan="1" style="text-align: right; font-weight: bold; color: white;">TOTAIS: ${lancamentos.length}</td> <td style="font-weight: bold; color: white; text-align: center;">${totalQuantidade > 0 ? formatarNumero(totalQuantidade) : '-'}</td> <td style="font-weight: bold; color: white; text-align: center;">${totalPeso > 0 ? formatarNumero(totalPeso) : '-'}</td> <td style="font-weight: bold; color: white; text-align: center;">${formatarValorReal(totalValor)}</td> <td colspan="1" style="color: white;"></td> <td colspan="1" style="color: white;"></td> </tr> `; return ` <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Relatório Financeiro - Lenha</title> <style> @page { size: landscape; margin: 10mm; } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: Arial, sans-serif; background: white; color: #000; padding: 15px; } .print-header-financeiro { display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 3px solid #2d5016; } .print-logo-financeiro { height: 50px; } .print-title-financeiro { font-size: 20px; font-weight: bold; color: #2d5016; text-align: center; flex-grow: 1; } .print-info-financeiro { margin-bottom: 10px; font-size: 10px; color: #333; background: #f5f5f5; padding: 8px; border-radius: 5px; } .print-info-financeiro strong { color: #2d5016; } .print-table-financeiro { width: 100%; border-collapse: collapse; font-size: 7px; margin-top: 8px; } .print-table-financeiro th { background: #2d5016; color: white; padding: 6px 3px; text-align: center; border: 1px solid #2d5016; font-weight: bold; font-size: 7px; white-space: nowrap; } .print-table-financeiro td { padding: 4px 3px; border: 1px solid #ccc; text-align: center; color: #000; background: white; font-size: 7px; white-space: nowrap; } .print-table-financeiro tbody tr:nth-child(even) { background: #f9f9f9; } .print-status-badge { padding: 2px 6px; border-radius: 10px; font-weight: bold; font-size: 6px; display: inline-block; } .print-status-pago { background: #c8e6c9; color: #2e7d32; border: 1px solid #2e7d32; } .print-status-aberto { background: #ffcdd2; color: #c62828; border: 1px solid #c62828; } .print-footer-financeiro { margin-top: 15px; padding-top: 8px; border-top: 2px solid #2d5016; font-size: 9px; color: #666; text-align: center; } .print-totais { margin-top: 12px; display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; font-size: 10px; } .print-total-card { background: #f5f5f5; padding: 8px; border-radius: 5px; border-left: 4px solid #2d5016; } .print-total-card strong { display: block; color: #2d5016; margin-bottom: 4px; font-size: 9px; } .print-total-card .valor { font-size: 12px; font-weight: bold; color: #333; } </style> </head> <body> <div class="print-area-financeiro"> <div class="print-header-financeiro"> <img src="${window._logoUrl||''}" alt="Logo" class="print-logo-financeiro"> <div class="print-title-financeiro">RELATÓRIO FINANCEIRO - PAGAMENTO DE LENHA</div> </div> <div class="print-info-financeiro"> <div style="display: flex; justify-content: space-between; margin-bottom: 4px;"> <div><strong>Data/Hora da Impressão:</strong> ${dataHoraImpressao}</div> <div><strong>Total de Registros:</strong> ${lancamentos.length}</div> </div> <div><strong>Filtros Aplicados:</strong> ${filtrosAtivos}</div> </div> <table class="print-table-financeiro"> <thead> <tr> <th>Status</th> <th>Pagamento</th> <th>NF</th> <th>Pedido</th> <th>Data</th> <th>Hora</th> <th>Código</th> <th>Fornecedor</th> <th>Produto</th> <th>Veículo</th> <th>Placa</th> <th>Qtd (m³)</th> <th>Qtd (Kg)</th> <th>Valor Total</th> <th>Local</th> <th>Obs</th> </tr> </thead> <tbody>${linhasTabela}</tbody> </table> <div class="print-totais"> <div class="print-total-card"> <strong>Total de Registros</strong> <div class="valor">${lancamentos.length}</div> </div> <div class="print-total-card"> <strong>Quantidade Total (m³)</strong> <div class="valor">${totalQuantidade > 0 ? formatarNumero(totalQuantidade) : '-'}</div> </div> <div class="print-total-card"> <strong>Peso Total (Kg)</strong> <div class="valor">${totalPeso > 0 ? formatarNumero(totalPeso) : '-'}</div> </div> <div class="print-total-card"> <strong>Valor Total (R$)</strong> <div class="valor">${formatarValorReal(totalValor)}</div> </div> </div> <div class="print-footer-financeiro"> Sistema de Pagamento de Lenha - Relatório gerado automaticamente em ${dataHoraImpressao} </div> </div> </body> </html> `; } function gerarHTMLImpressaoDashboard(filtrosAtivos) { const dataHoraImpressao = new Date().toLocaleString('pt-BR'); // Capturar valores dos cards - CORRIGIDO const qtdeLenha = document.getElementById('qtdeLenha') ? document.getElementById('qtdeLenha').textContent : '0'; const qtdeCascaArroz = document.getElementById('qtdeCascaArroz') ? document.getElementById('qtdeCascaArroz').textContent : '0'; const valorLenha = document.getElementById('valorLenha') ? document.getElementById('valorLenha').textContent : '0,00'; const valorCascaArroz = document.getElementById('valorCascaArroz') ? document.getElementById('valorCascaArroz').textContent : '0,00'; const totalPago = document.getElementById('totalPago') ? document.getElementById('totalPago').textContent : '0,00'; const totalAPagar = document.getElementById('totalAPagar') ? document.getElementById('totalAPagar').textContent : '0,00'; // Capturar gráficos como imagens const chartLocal = document.getElementById('chartLocal') ? document.getElementById('chartLocal').toDataURL('image/png') : ''; const chartProduto = document.getElementById('chartProduto') ? document.getElementById('chartProduto').toDataURL('image/png') : ''; const chartEmpresaQtd = document.getElementById('chartEmpresaQtd') ? document.getElementById('chartEmpresaQtd').toDataURL('image/png') : ''; const chartEmpresaValor = document.getElementById('chartEmpresaValor') ? document.getElementById('chartEmpresaValor').toDataURL('image/png') : ''; const chartFornecedorQtd = document.getElementById('chartFornecedorQtd') ? document.getElementById('chartFornecedorQtd').toDataURL('image/png') : ''; const chartFornecedorValor = document.getElementById('chartFornecedorValor') ? document.getElementById('chartFornecedorValor').toDataURL('image/png') : ''; const chartPrecoLenha = document.getElementById('chartPrecoLenha') ? document.getElementById('chartPrecoLenha').toDataURL('image/png') : ''; const chartPrecoCasca = document.getElementById('chartPrecoCasca') ? document.getElementById('chartPrecoCasca').toDataURL('image/png') : ''; return ` <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Dashboard - Relatório</title> <style> @page { size: portrait; margin: 8mm; } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: Arial, sans-serif; background: white; color: #000; padding: 8px; font-size: 10px; } .print-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; padding-bottom: 6px; border-bottom: 2px solid #2d5016; } .print-logo { height: 36px; } .print-title { font-size: 16px; font-weight: bold; color: #2d5016; text-align: center; flex-grow: 1; } .print-info { margin-bottom: 8px; font-size: 8px; color: #333; background: #f5f5f5; padding: 6px; border-radius: 3px; } .print-info strong { color: #2d5016; } .print-cards { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin: 8px 0; } .print-card { background: #f5f5f5; padding: 8px; border-radius: 4px; border-left: 3px solid #2d5016; } .print-card h3 { color: #666; font-size: 8px; margin-bottom: 4px; } .print-card strong { color: #2d5016; font-size: 9px; display: block; margin-bottom: 6px; } .print-card .valor { font-size: 14px; font-weight: bold; color: #2d5016; } .print-charts { margin-top: 8px; } .print-chart-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 8px; page-break-inside: avoid; } .print-chart-full { margin-bottom: 8px; page-break-inside: avoid; } .print-chart-container { background: #f9f9f9; padding: 6px; border-radius: 3px; text-align: center; border: 1px solid #e0e0e0; } .print-chart-container h3 { color: #2d5016; font-size: 9px; margin-bottom: 4px; font-weight: bold; } .print-chart-container img { max-width: 100%; height: auto; max-height: 280px; } .print-chart-container.full img { max-height: 300px; } .print-footer { margin-top: 8px; padding-top: 6px; border-top: 2px solid #2d5016; font-size: 8px; color: #666; text-align: center; } </style> </head> <body> <div class="print-header"> <img src="${window._logoUrl||''}" alt="Logo" class="print-logo"> <div class="print-title">DASHBOARD - PAGAMENTO DE LENHA</div> </div> <div class="print-info"> <div style="display: flex; justify-content: space-between;"> <div><strong>Impressão:</strong> ${dataHoraImpressao}</div> <div><strong>Filtros:</strong> ${filtrosAtivos}</div> </div> </div> <div class="print-cards"> <div class="print-card"> <strong>Qtde Recebida</strong> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; font-size: 9px;"> <div style="text-align: center;"> <div style="color: #666; margin-bottom: 3px;">Lenha</div> <div class="valor" style="font-size: 12px;">${qtdeLenha}</div> </div> <div style="text-align: center;"> <div style="color: #666; margin-bottom: 3px;">Casca Arroz</div> <div class="valor" style="font-size: 12px;">${qtdeCascaArroz}</div> </div> </div> </div> <div class="print-card"> <strong>Valor Total</strong> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; font-size: 9px;"> <div style="text-align: center;"> <div style="color: #666; margin-bottom: 3px;">Lenha</div> <div class="valor" style="font-size: 12px;">${valorLenha}</div> </div> <div style="text-align: center;"> <div style="color: #666; margin-bottom: 3px;">Casca Arroz</div> <div class="valor" style="font-size: 12px;">${valorCascaArroz}</div> </div> </div> </div> <div class="print-card"> <strong>Total Pago (R$)</strong> <div class="valor">${totalPago}</div> </div> <div class="print-card"> <strong>Total a Pagar (R$)</strong> <div class="valor">${totalAPagar}</div> </div> </div> <div class="print-charts"> <div class="print-chart-row"> <div class="print-chart-container"> <h3>Quantidade por Local (Kg)</h3> <img src="${chartLocal}" alt="Gráfico Local"> </div> <div class="print-chart-container"> <h3>Quantidade por Produto (Kg)</h3> <img src="${chartProduto}" alt="Gráfico Produto"> </div> </div> <div class="print-chart-row"> <div class="print-chart-container"> <h3>Qtd por Empresa - Últimos 6 Meses (Kg)</h3> <img src="${chartEmpresaQtd}" alt="Gráfico Empresa Qtd"> </div> <div class="print-chart-container"> <h3>Valor por Empresa - Últimos 6 Meses</h3> <img src="${chartEmpresaValor}" alt="Gráfico Empresa Valor"> </div> </div> <div class="print-chart-row"> <div class="print-chart-container"> <h3>Qtd por Fornecedor (Kg)</h3> <img src="${chartFornecedorQtd}" alt="Gráfico Fornecedor Qtd"> </div> <div class="print-chart-container"> <h3>Valor por Fornecedor</h3> <img src="${chartFornecedorValor}" alt="Gráfico Fornecedor Valor"> </div> </div> <div class="print-chart-row"> <div class="print-chart-container"> <h3>Variação de Preço Médio da Lenha (R$/Kg)</h3> <img src="${chartPrecoLenha}" alt="Gráfico Preço Lenha"> </div> <div class="print-chart-container"> <h3>Variação de Preço Médio da Casca de Arroz (R$/Kg)</h3> <img src="${chartPrecoCasca}" alt="Gráfico Preço Casca"> </div> </div> </div> <div class="print-footer"> Sistema de Pagamento de Lenha - Relatório gerado em ${dataHoraImpressao} </div> </body> </html> `; } </script> </body> </html>. (line 4, file "Código")