pode fazer um novo css e html mais bonito para meu codigo - <!DOCTYPE html> <html lang="pt-br"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Controle de Consumo de Funcionários</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.6.2/sql-wasm.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="https://unpkg.com/lucide@latest"></script> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" /> <style> :root { /* Tema Claro (Padrão) */ --primary-color: #4CAF50; --primary-dark: #388E3C; --secondary-color: #2196F3; --accent-color: #FFC107; --text-color: #333; --light-text-color: #666; --background-light: #f8f9fa; --background-medium: #e9ecef; --surface-color: #ffffff; --border-color: #dee2e6; --shadow-light: rgba(0, 0, 0, 0.08); --shadow-medium: rgba(0, 0, 0, 0.15); --delete-color: #dc3545; --delete-dark: #c82333; --edit-color: #2196F3; --edit-dark: #1976D2; --toggle-color: #ffc107; --toggle-dark: #e0a800; --input-focus-shadow: rgba(76, 175, 80, 0.35); } html[data-theme='dark'] { /* Tema Escuro */ --primary-color: #5cb85c; --primary-dark: #449d44; --secondary-color: #337ab7; --accent-color: #f0ad4e; --text-color: #e0e0e0; --light-text-color: #a0a0a0; --background-light: #121212; --background-medium: #1e1e1e; --surface-color: #2c2c2c; --border-color: #444444; --shadow-light: rgba(255, 255, 255, 0.05); --shadow-medium: rgba(255, 255, 255, 0.1); --delete-color: #d9534f; --delete-dark: #c9302c; --edit-color: #5bc0de; --edit-dark: #31b0d5; --toggle-color: #f0ad4e; --toggle-dark: #ec971f; --input-focus-shadow: rgba(92, 184, 92, 0.35); } html { transition: background-color 0.3s, color 0.3s; } body { font-family: 'Inter', sans-serif; padding: 30px; background-color: var(--background-light); color: var(--text-color); line-height: 1.6; margin: 0; display: flex; flex-direction: column; align-items: center; min-height: 100vh; padding-bottom: 80px; padding-top: 80px; /* Adicionado para acomodar a navbar fixa */ } h1 { color: var(--primary-dark); margin-bottom: 25px; font-size: 2.5em; font-weight: 700; text-align: center; width: 100%; max-width: 1000px; text-shadow: 1px 1px 2px rgba(0,0,0,0.05); } h2, h3 { color: var(--text-color); margin-bottom: 20px; font-size: 1.8em; font-weight: 600; border-bottom: 2px solid var(--border-color); padding-bottom: 10px; } h3 { border-bottom: none; font-size: 1.5em; margin-top: 20px; } label { display: block; margin: 18px 0 8px; font-weight: 600; color: var(--text-color); letter-spacing: 0.5px; text-shadow: 0.5px 0.5px 1px rgba(0,0,0,0.02); } input, select { padding: 14px 18px; margin-bottom: 20px; width: 100%; max-width: 480px; box-sizing: border-box; border: 1px solid var(--border-color); border-radius: 12px; font-size: 1.05em; color: var(--text-color); background-color: var(--surface-color); transition: border-color 0.3s ease, box-shadow 0.3s ease, background-color 0.3s ease; } input::placeholder { color: var(--light-text-color); opacity: 0.7; } html[data-theme='light'] input:hover, html[data-theme='light'] select:hover { background-color: #fcfcfc; border-color: #c0c0c0; } html[data-theme='dark'] input:hover, html[data-theme='dark'] select:hover { border-color: #666; } input:focus, select:focus { border-color: var(--primary-color); box-shadow: 0 0 0 4px var(--input-focus-shadow); outline: none; background-color: var(--surface-color); } button { padding: 12px 20px; background-color: var(--primary-color); color: white; border: none; border-radius: 12px; cursor: pointer; transition: background-color 0.3s ease, transform 0.2s ease, box-shadow 0.3s ease; font-weight: 600; font-size: 1em; display: inline-flex; align-items: center; justify-content: center; gap: 8px; margin-bottom: 0; box-shadow: 0 5px 15px var(--shadow-light); text-shadow: 0.5px 0.5px 1px rgba(0,0,0,0.1); background-image: linear-gradient(to bottom right, var(--primary-color), var(--primary-dark)); } i[data-lucide] { width: 1.1em; height: 1.1em; } button:hover { background-color: var(--primary-dark); transform: translateY(-4px); box-shadow: 0 8px 20px var(--shadow-medium); background-image: linear-gradient(to bottom right, var(--primary-dark), #2e7d32); } button:active { transform: translateY(0); box-shadow: 0 2px 8px var(--shadow-light); } /* ===== NOVOS BOTÕES GENÉRICOS ===== */ .btn-primary { background-color: #007bff; background-image: linear-gradient(to bottom right, #007bff, #0056b3); color: white; } .btn-primary:hover { background-color: #0056b3; background-image: linear-gradient(to bottom right, #0056b3, #004085); } .btn-secondary { background-color: transparent; background-image: none; color: var(--light-text-color); border: 1px solid var(--border-color); box-shadow: none; } .btn-secondary:hover { background-color: var(--background-medium); border-color: var(--border-color); color: var(--text-color); box-shadow: none; transform: translateY(-2px); } button.activate-btn { background-color: var(--primary-color); background-image: linear-gradient(to bottom right, var(--primary-color), var(--primary-dark)); } button.activate-btn:hover { background-image: linear-gradient(to bottom right, var(--primary-dark), #2e7d32); } button.toggle-btn { background-color: var(--toggle-color); background-image: linear-gradient(to bottom right, var(--toggle-color), var(--toggle-dark)); } button.toggle-btn:hover { background-color: var(--toggle-dark); background-image: linear-gradient(to bottom right, var(--toggle-dark), #c79100); } button.delete-btn { background-color: var(--delete-color); background-image: linear-gradient(to bottom right, var(--delete-color), var(--delete-dark)); } button.delete-btn:hover { background-color: var(--delete-dark); background-image: linear-gradient(to bottom right, var(--delete-dark), #a71d2a); } button.edit-btn { background-color: var(--edit-color); background-image: linear-gradient(to bottom right, var(--edit-color), var(--edit-dark)); } button.edit-btn:hover { background-color: var(--edit-dark); background-image: linear-gradient(to bottom right, var(--edit-dark), #1565C0); } table { width: 100%; border-collapse: separate; border-spacing: 0; margin-top: 25px; background-color: var(--surface-color); border-radius: 12px; overflow: hidden; box-shadow: 0 4px 12px var(--shadow-light); } tr { transition: background-color 0.2s ease-in-out; } th, td { border-bottom: 1px solid var(--border-color); padding: 15px 20px; text-align: left; vertical-align: middle; } td:last-child { display: flex; justify-content: flex-end; gap: 8px; } th { background-color: var(--background-medium); font-weight: 600; color: var(--light-text-color); text-transform: uppercase; font-size: 0.9em; } tr:last-child td { border-bottom: none; } td:last-child button { padding: 10px; margin-left: 0; gap: 0; } td:last-child button i { margin: 0; } html[data-theme='light'] tr:nth-child(even) { background-color: #fefefe; } html[data-theme='dark'] tr:nth-child(even) { background-color: #383838; } html[data-theme='light'] tr:hover { background-color: #f0f4f7; } html[data-theme='dark'] tr:hover { background-color: var(--primary-dark); color: white; } html[data-theme='dark'] tr:hover td { color: white; } .inactive-row { opacity: 0.6; background-color: var(--background-medium) !important; } .inactive-row td { text-decoration: line-through; } .tabs { display: flex; width: 100%; max-width: 1000px; margin-bottom: 30px; background-color: var(--background-medium); border-radius: 12px; padding: 4px; box-shadow: 0 4px 8px var(--shadow-light); flex-wrap: nowrap; overflow-x: auto; /* Propriedades para fixar a navbar */ position: fixed; top: 0; left: 50%; transform: translateX(-50%); z-index: 1000; border-radius: 0 0 12px 12px; } .tabs::-webkit-scrollbar { height: 4px; } .tabs::-webkit-scrollbar-thumb { background: var(--border-color); border-radius: 10px; } .tab-button { background-color: transparent; padding: 12px 20px; cursor: pointer; border-radius: 8px; user-select: none; transition: background-color 0.3s ease, color 0.3s ease, box-shadow 0.3s ease; font-weight: 500; color: var(--light-text-color); flex-grow: 1; flex-shrink: 0; text-align: center; border: none; margin: 0; display: inline-flex; align-items: center; justify-content: center; gap: 8px; white-space: nowrap; } .tab-button.active { background-color: var(--surface-color); color: var(--primary-color); font-weight: 700; box-shadow: 0 2px 8px var(--shadow-light); z-index: 1; } .section { display: none; background: var(--surface-color); border-radius: 12px; box-shadow: 0 6px 18px var(--shadow-medium); padding: 30px; margin-bottom: 40px; width: 100%; max-width: 1000px; box-sizing: border-box; } .section.active { display: block; } #toast { visibility: hidden; min-width: 280px; background-color: #dc3545; /* Vermelho */ color: #fff; /* Cor do texto branca */ text-align: center; border-radius: 10px; padding: 16px 25px; position: fixed; z-index: 9999; left: 50%; bottom: 40px; font-size: 1.1em; transform: translateX(-50%); opacity: 0; transition: opacity 0.6s ease, visibility 0.6s ease; box-shadow: 0 4px 12px var(--shadow-medium); pointer-events: none; } #toast.show { visibility: visible; opacity: 1; pointer-events: auto; } .modal { display: none; position: fixed; z-index: 1001; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.5); justify-content: center; align-items: center; padding: 20px; } .modal-content { background-color: var(--surface-color); padding: 35px; border-radius: 15px; box-shadow: 0 8px 25px var(--shadow-medium); text-align: center; animation: fadeIn 0.4s ease-out; width: 90%; max-width: 450px; } .modal-content h3 { margin-top: 0; color: var(--primary-dark); font-size: 1.8em; margin-bottom: 15px; } .modal-content p { margin-bottom: 30px; color: var(--light-text-color); font-size: 1.1em; } .modal-buttons { display: flex; justify-content: center; gap: 20px; } .modal-buttons button { flex: 1; max-width: 180px; padding: 12px 25px; font-size: 1.05em; margin-bottom: 0; } .modal-buttons .cancel-btn { background-color: #6c757d; background-image: none; box-shadow: 0 4px 8px rgba(0,0,0,0.1); } .modal-buttons .cancel-btn:hover { background-color: #5a6268; box-shadow: 0 6px 12px rgba(0,0,0,0.2); } .modal-content.form-modal { text-align: left; } .modal-content.form-modal input, .modal-content.form-modal select { max-width: 100%; } .modal-content.form-modal .modal-buttons { margin-top: 20px; } footer { margin-top: 30px; padding: 1px; width: 100%; max-width: 1000px; text-align: center; color: var(--light-text-color); font-size: 0.9em; border-top: 1px solid var(--border-color); background-color: var(--background-medium); border-radius: 12px 12px 0 0; box-shadow: 0 -2px 8px var(--shadow-light); position: fixed; bottom: 0; left: 50%; transform: translateX(-50%); z-index: 999; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(-30px); } to { opacity: 1; transform: translateY(0); } } .select2-container--default .select2-selection--single { background-color: var(--surface-color); border: 1px solid var(--border-color); border-radius: 12px; height: auto; } .select2-container--default .select2-selection--single .select2-selection__rendered { color: var(--text-color); line-height: 1.6; padding: 10px 18px; } .select2-container--default .select2-selection--single .select2-selection__arrow { height: 50px; right: 10px; } .select2-container .select2-selection--single .select2-selection__rendered { padding-right: 30px; } .select2-dropdown { background-color: var(--surface-color); border: 1px solid var(--border-color); border-radius: 12px; box-shadow: 0 8px 25px var(--shadow-medium); } .select2-container--default .select2-search--dropdown .select2-search__field { background-color: var(--background-medium); border: 1px solid var(--border-color); color: var(--text-color); border-radius: 8px; padding: 8px 12px; } .select2-container--default .select2-search--dropdown .select2-search__field:focus { border-color: var(--primary-color); outline: none; } .select2-container--default .select2-results__option--highlighted[aria-selected] { background-color: var(--primary-color); color: white; } .select2-results__option { color: var(--text-color); } .select2-container--default .select2-results__option[aria-selected=true] { background-color: var(--background-medium); } .select2-container--open .select2-selection--single { border-color: var(--primary-color); box-shadow: none; } .modal-content .close-modal-btn { position: absolute; top: 10px; right: 15px; background: none; border: none; font-size: 2.5em; color: var(--light-text-color); cursor: pointer; line-height: 1; padding: 0; font-weight: 300; transition: color 0.2s ease, transform 0.2s ease; box-shadow: none; background-image: none; } .modal-content .close-modal-btn:hover { color: var(--delete-color); transform: scale(1.1); background-color: transparent; } .modal-header { text-align: center; margin-bottom: 25px; } .modal-header h3 { margin: 0; border-bottom: none; } .form-group { margin-bottom: 22px; } .form-group label { margin-top: 0; margin-bottom: 10px; } .form-row { display: flex; gap: 20px; align-items: flex-start; } .form-row .form-group { flex: 1; margin-bottom: 22px; } .quantity-input { display: flex; align-items: center; border: 1px solid var(--border-color); border-radius: 12px; overflow: hidden; transition: border-color 0.3s ease, box-shadow 0.3s ease; } .quantity-input:focus-within { border-color: var(--primary-color); box-shadow: 0 0 0 4px var(--input-focus-shadow); } .quantity-input input[type="number"] { width: 100%; text-align: center; border: none; margin-bottom: 0; box-shadow: none; padding: 14px 5px; -moz-appearance: textfield; } .quantity-input input[type="number"]::-webkit-outer-spin-button, .quantity-input input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } .quantity-btn { background-color: var(--background-medium); border: none; color: var(--text-color); cursor: pointer; font-size: 1.4em; font-weight: 600; padding: 0 18px; align-self: stretch; transition: background-color 0.3s, color 0.3s; box-shadow: none; transform: none; background-image: none; margin-bottom: 0; border-radius: 0; } .quantity-btn:hover { background-color: var(--primary-color); color: white; } .quantity-btn.minus { border-right: 1px solid var(--border-color); } .quantity-btn.plus { border-left: 1px solid var(--border-color); } .modal-content.form-modal { max-width: 550px; padding: 30px 40px; position: relative; } .modal-content.form-modal .modal-buttons { margin-top: 25px; justify-content: flex-end; gap: 15px; } .modal-content.form-modal .cancel-btn { background-color: transparent; background-image: none; color: var(--light-text-color); border: 1px solid var(--border-color); box-shadow: none; } .modal-content.form-modal .cancel-btn:hover { background-color: var(--background-medium); border-color: var(--border-color); color: var(--text-color); box-shadow: none; transform: translateY(-2px); } .filter-container { display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-end; padding: 20px; margin-bottom: 20px; background-color: transparent; border: 1px solid var(--border-color); border-radius: 12px; } .filter-group { display: flex; flex-direction: column; gap: 8px; } .filter-group.search-group { flex: 1 1 300px; } .filter-group.date-group { display: flex; flex-direction: row; gap: 16px; } .filter-group label { margin: 0; font-size: 0.9em; font-weight: 500; color: var(--light-text-color); } .filter-container input, .filter-container button { margin-bottom: 0; } .input-with-icon { position: relative; display: flex; align-items: center; } .input-with-icon i { position: absolute; left: 15px; color: var(--light-text-color); width: 18px; height: 18px; } .input-with-icon input { padding-left: 55px; /* MODIFICADO: Aumentado para dar mais espaço */ } .settings-list { display: flex; flex-direction: column; gap: 16px; } .setting-item { display: flex; justify-content: space-between; align-items: center; padding: 20px; background-color: var(--background-medium); border-radius: 12px; border: 1px solid var(--border-color); flex-wrap: wrap; gap: 16px; opacity: 0; transform: translateY(10px); animation: fadeInItem 0.4s ease-out forwards; } .setting-item:nth-child(1) { animation-delay: 0.1s; } .setting-item:nth-child(2) { animation-delay: 0.2s; } .setting-item:nth-child(3) { animation-delay: 0.3s; } .setting-item:nth-child(4) { animation-delay: 0.4s; }
@keyframes fadeInItem { to { opacity: 1; transform: translateY(0); } } .setting-info h3 { margin: 0 0 4px 0; font-size: 1.1em; font-weight: 600; color: var(--text-color); border: none; padding: 0; } .setting-info p { margin: 0; font-size: 0.9em; color: var(--light-text-color); } #loading-overlay { position: fixed; inset: 0; background-color: rgba(0, 0, 0, 0.5); z-index: 9999; display: flex; justify-content: center; align-items: center; } .spinner { width: 50px; height: 50px; border: 5px solid var(--surface-color); border-top-color: var(--primary-color); border-radius: 50%; animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } } .dashboard-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 30px; } .dashboard-card { background-color: var(--background-medium); padding: 20px; border-radius: 12px; border: 1px solid var(--border-color); } .dashboard-card h4 { font-size: 1em; color: var(--light-text-color); margin-bottom: 8px; font-weight: 500; } .dashboard-card .value { font-size: 2em; font-weight: 700; color: var(--text-color); } .dashboard-card .subtext { font-size: 0.9em; color: var(--light-text-color); } .chart-container { height: 350px; margin-top: 30px; } .report-controls { padding: 24px; background-color: var(--background-medium); border: 1px solid var(--border-color); border-radius: 12px; display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; /* Alterado de 20px para 15px para um layout mais compacto, conforme sugestão */ align-items: center; /* ALTEADO: Alinha os itens do grid ao centro verticalmente */ margin-bottom: 30px; } .report-controls .filter-group, .report-controls .actions-group { display: flex; flex-direction: column; gap: 8px; } .report-controls .actions-group { flex-direction: row; gap: 12px; align-items: center; /* ADICIONADO: Alinha os itens (botões) dentro do actions-group verticalmente ao centro */ } .report-controls .actions-group button { flex: 1; height: 100%; /* ADICIONADO: Garante que os botões tenham a mesma altura do elemento de entrada */ margin-bottom: 0; /* Garante que não haja margin-bottom extra */ display: flex; /* Para centralizar o ícone e o texto verticalmente */ align-items: center; /* Centraliza o conteúdo (ícone e texto) */ justify-content: center; /* Centraliza o conteúdo (ícone e texto) */ box-sizing: border-box; /* Garante que padding e border sejam incluídos na altura */ } .report-controls label { margin: 0; font-size: 0.9em; font-weight: 500; } /* Ajuste para inputs dentro do report-controls para remover margin-bottom padrão */ .report-controls input, .report-controls select { margin-bottom: 0; } #relatorio .empty-state { text-align: center; padding: 60px 20px; border: 2px dashed var(--border-color); border-radius: 12px; margin-top: 30px; } #relatorio .empty-state i { font-size: 3em; color: var(--border-color); width: 1.5em; height: 1.5em; } #relatorio .empty-state h3 { margin-top: 20px; border: none; font-size: 1.2em; color: var(--text-color); } #relatorio .empty-state p { color: var(--light-text-color); max-width: 300px; margin: 8px auto 0; } /* Novas regras para o "olhinho" de senha */ .input-with-toggle-password { position: relative; width: 100%; max-width: 600px; /* Aumentado para preencher mais o modal. Ajuste este valor. */ margin: 0 auto 20px auto; } .input-with-toggle-password input { width: 100%; padding-right: 45px; margin-bottom: 0; } .toggle-password-icon { position: absolute; right: 15px; top: 50%; transform: translateY(-50%); cursor: pointer; color: var(--light-text-color); width: 20px; height: 20px; transition: color 0.2s ease; } .toggle-password-icon:hover { color: var(--text-color); } /* Adicionado para espaçamento do botão Novo Lançamento */ .section button.btn-primary:first-of-type { margin-bottom: 25px; }
@media (max-width: 768px) { body { padding: 20px; padding-top: 70px; /* Adjust padding for smaller tabs */ padding-bottom: 70px; } h1 { font-size: 2em; } .tabs { padding: 2px; border-radius: 0 0 8px 8px; } .tab-button { padding: 10px 15px; font-size: 0.9em; } .section { padding: 20px; margin-bottom: 30px; } input, select, button { font-size: 0.95em; padding: 12px 15px; } button { padding: 10px 15px; } table { font-size: 0.9em; } th, td { padding: 12px 15px; } td:last-child { flex-direction: column; /* Stack action buttons */ gap: 5px; align-items: flex-end; } td:last-child button { width: fit-content; align-self: flex-end; } .modal-content { padding: 25px; width: 95%; } .modal-buttons { flex-direction: column; gap: 10px; } .modal-buttons button { max-width: 100%; } .form-row { flex-direction: column; gap: 15px; } .filter-container { flex-direction: column; align-items: stretch; padding: 15px; gap: 10px; } .filter-group.date-group { flex-direction: column; gap: 10px; } .input-with-icon input { padding-left: 45px; /* Adjust for smaller screens */ } .report-controls { grid-template-columns: 1fr; padding: 15px; gap: 15px; } .report-controls .actions-group { flex-direction: column; gap: 10px; } .dashboard-grid { grid-template-columns: 1fr; /* Stack cards on mobile */ } .dashboard-card .value { font-size: 1.8em; } .chart-container { height: 300px; /* Adjust chart height for smaller screens */ } .setting-item { flex-direction: column; align-items: flex-start; gap: 10px; padding: 15px; } .setting-action { width: 100%; display: flex; justify-content: flex-end; } #toast { min-width: unset; width: 90%; left: 5%; transform: translateX(0%); bottom: 20px; font-size: 1em; padding: 12px 20px; } footer { padding: 5px; font-size: 0.8em; } }
@media print { /* Estilos de impressão mantidos */ body { padding: 0; background-color: #fff; color: #000; margin: 0; min-height: auto; } .tabs, .tab-button, .section:not(.active), .modal, #toast, #loading-overlay, footer, button, .filter-container, .report-controls { display: none !important; } .section.active { display: block !important; box-shadow: none; padding: 0; margin-bottom: 0; max-width: none; width: auto; } h2 { border-bottom: none; text-align: center; margin-bottom: 20px; font-size: 1.5em; } table { width: 100%; border-collapse: collapse; box-shadow: none; border-radius: 0; } th, td { border: 1px solid #ccc; padding: 8px; font-size: 0.9em; color: #000; background-color: #fff !important; } th { background-color: #f2f2f2 !important; font-weight: bold; } tr:nth-child(even) { background-color: #f9f9f9 !important; } td:last-child { display: table-cell; /* Revert flex for print */ text-align: left; } .dashboard-grid, .chart-container, .settings-list { display: none !important; } #relatorio { margin-top: 0; } #relatorio .empty-state { display: none !important; } } </style> </head> <body> <div class="tabs"> <div class="tab-button active" onclick="showTab(0)"><i data-lucide="layout-dashboard"></i>Visão Geral</div> <div class="tab-button" onclick="showTab(1)"><i data-lucide="users"></i>Funcionários</div> <div class="tab-button" onclick="showTab(2)"><i data-lucide="package"></i>Produtos</div> <div class="tab-button" onclick="showTab(3)"><i data-lucide="arrow-right-left"></i>Lançamentos</div> <div class="tab-button" onclick="showTab(4)"><i data-lucide="bar-chart-3"></i>Relatório</div> <div class="tab-button" onclick="showTab(5)"><i data-lucide="settings"></i>Configurações</div> </div> <div class="section active"> <h2><i data-lucide="layout-dashboard"></i> Dashboard do Mês</h2> <div class="dashboard-grid"> <div class="dashboard-card" id="card-total-mes"> <h4>Total Gasto no Mês</h4> <p class="value">R$ 0,00</p> <p class="subtext">Consumo geral</p> </div> <div class="dashboard-card" id="card-top-produto"> <h4>Produto Mais Consumido</h4> <p class="value">-</p> <p class="subtext">Item mais retirado</p> </div> <div class="dashboard-card" id="card-top-consumidor"> <h4>Top Consumidor do Mês</h4> <p class="value">-</p> <p class="subtext">Maior consumo em valor</p> </div> </div> <div class="chart-container"> <canvas id="mainDashboardChart"></canvas> </div> </div> <div class="section"> <h2><i data-lucide="users"></i> Gestão de Funcionários</h2> <button class="btn-primary" onclick="openAddFuncionarioModal()"><i data-lucide="plus"></i>Novo Funcionário</button> <div id="lista-funcionarios"></div> </div> <div class="section"> <h2><i data-lucide="package"></i> Gestão de Produtos</h2> <button class="btn-primary" onclick="openAddProdutoModal()"><i data-lucide="plus"></i>Novo Produto</button> <div id="lista-produtos"></div> </div> <div class="section"> <h2><i data-lucide="arrow-right-left"></i> Gestão de Lançamentos</h2> <button class="btn-primary" onclick="openLancamentoModal()"><i data-lucide="plus"></i>Novo Lançamento</button> <div class="filter-container"> <div class="filter-group search-group"> <label for="lanc-pesquisa">Pesquisar por nome ou observação</label> <div class="input-with-icon"> <i data-lucide="search"></i> <input type="text" id="lanc-pesquisa" placeholder="Funcionário, produto, obs..." oninput="listarLancamentos()"> </div> </div> <div class="filter-group date-group"> <div class="date-field"> <label for="lanc-filtro-de">Data de Início</label> <input type="date" id="lanc-filtro-de"> </div> <div class="date-field"> <label for="lanc-filtro-ate">Data Final</label> <input type="date" id="lanc-filtro-ate"> </div> </div> <div class="filter-group actions-group"> <button class="btn-primary" onclick="listarLancamentos(true)"><i data-lucide="filter"></i>Filtrar</button> <button class="btn-secondary" onclick="goToPreviousMonth()"><i data-lucide="chevrons-left"></i>Mês Anterior</button> <button class="btn-secondary" onclick="resetarFiltrosLancamentos()"><i data-lucide="rotate-cw"></i>Mês Atual</button> </div> </div> <div id="lista-lancamentos"></div> </div> <div class="section"> <h2><i data-lucide="bar-chart-3"></i> Relatório</h2> <div class="report-controls"> <div class="filter-group"> <label for="filtro-func">Filtrar por Funcionário</label> <select id="filtro-func"></select> </div> <div class="filter-group"> <label for="filtro-de">Período de</label> <input id="filtro-de" type="date"> </div> <div class="filter-group"> <label for="filtro-ate">Até</label> <input id="filtro-ate" type="date"> </div> <div class="actions-group"> <button class="btn-primary" onclick="gerarRelatorio()"><i data-lucide="file-text"></i>Gerar</button> <button class="btn-secondary" onclick="imprimirRelatorio()"><i data-lucide="file-down"></i>PDF</button> </div> </div> <div id="relatorio"> <div class="empty-state"> <i data-lucide="search-check"></i> <h3>Nenhum relatório gerado</h3> <p>Utilize o painel de controle acima para gerar um novo relatório.</p> </div> </div> </div> <div class="section"> <h2><i data-lucide="settings"></i> Configurações</h2> <div class="settings-list"> <div class="setting-item"> <div class="setting-info"> <h3>Alterar Tema</h3> <p>Alterne entre os modos de visualização claro e escuro.</p> </div> <div class="setting-action"> <button class="btn-secondary" id="theme-toggle-btn"><i data-lucide="sun"></i>Alterar Tema</button> </div> </div> <div class="setting-item"> <div class="setting-info"> <h3>Exportar Dados</h3> <p>Salve um backup completo de seus dados em um arquivo .sqlite.</p> </div> <div class="setting-action"> <input type="file" id="importFile" style="display:none" onchange="importarDB(event)"> <button class="btn-secondary" onclick="exportarDB()"><i data-lucide="download"></i>Exportar Banco</button> </div> </div> <div class="setting-item"> <div class="setting-info"> <h3>Importar Dados</h3> <p>Carregue um arquivo de backup .sqlite. (Atenção: isso substituirá os dados atuais).</p> </div> <div class="setting-action"> <input type="file" id="importFile" style="display:none" onchange="importarDB(event)"> <button class="btn-secondary" onclick="document.getElementById('importFile').click()"><i data-lucide="upload"></i>Importar Banco</button> </div> </div> <div class="setting-item"> <div class="setting-info"> <h3>Alterar Senha do Administrador</h3> <p>Defina uma nova senha para as ações restritas.</p> </div> <div class="setting-action"> <button class="btn-primary" onclick="openChangePasswordModal()"><i data-lucide="key-round"></i>Alterar Senha</button> </div> </div> </div> </div> <div id="loading-overlay" style="display: none;"><div class="spinner"></div></div> <div id="toast"></div> <div id="confirmationModal" class="modal"> <div class="modal-content"> <h3 id="modalTitle">Confirmação</h3> <p id="modalMessage">Você tem certeza?</p> <div class="modal-buttons"> <button class="btn-secondary" id="cancelButton"><i data-lucide="x-circle"></i>Cancelar</button><button class="delete-btn" id="confirmButton"><i data-lucide="check-circle"></i>Confirmar</button> </div> </div> </div> <div id="passwordModal" class="modal"> <div class="modal-content"> <button class="close-modal-btn" onclick="closePasswordModal()"><i data-lucide="x"></i></button> <div class="modal-header"> <h3>Acesso Restrito</h3> </div> <div class="form-group"> <label for="admin-password">Senha de Administrador:</label> <div class="input-with-toggle-password"> <input type="password" id="admin-password" placeholder="Digite a senha"> <i data-lucide="eye" class="toggle-password-icon" onclick="togglePasswordVisibility('admin-password', this)"></i> </div> </div> <div class="modal-buttons"> <button class="btn-secondary" onclick="closePasswordModal()"><i data-lucide="x"></i>Cancelar</button> <button class="btn-primary" id="confirmPasswordButton"><i data-lucide="check"></i>Confirmar</button> </div> </div> </div> <div id="changePasswordModal" class="modal"> <div class="modal-content form-modal"> <button class="close-modal-btn" onclick="closeChangePasswordModal()"><i data-lucide="x"></i></button> <div class="modal-header"> <h3>Alterar Senha do Administrador</h3> </div> <div class="form-group"> <label for="current-admin-password">Senha Atual:</label> <div class="input-with-toggle-password"> <input type="password" id="current-admin-password" placeholder="Digite a senha atual"> <i data-lucide="eye" class="toggle-password-icon" onclick="togglePasswordVisibility('current-admin-password', this)"></i> </div> </div> <div class="form-group"> <label for="new-admin-password">Nova Senha:</label> <div class="input-with-toggle-password"> <input type="password" id="new-admin-password" placeholder="Digite a nova senha"> <i data-lucide="eye" class="toggle-password-icon" onclick="togglePasswordVisibility('new-admin-password', this)"></i> </div> </div> <div class="form-group"> <label for="confirm-new-admin-password">Confirmar Nova Senha:</label> <div class="input-with-toggle-password"> <input type="password" id="confirm-new-admin-password" placeholder="Confirme a nova senha"> <i data-lucide="eye" class="toggle-password-icon" onclick="togglePasswordVisibility('confirm-new-admin-password', this)"></i> </div> </div> <div class="modal-buttons"> <button class="btn-secondary" onclick="closeChangePasswordModal()"><i data-lucide="x"></i>Cancelar</button> <button class="btn-primary" id="saveNewPasswordButton"><i data-lucide="save"></i>Salvar Nova Senha</button> </div> </div> </div> <div id="funcionarioFormModal" class="modal"> <div class="modal-content form-modal"> <h3 id="funcionarioModalTitle">Cadastrar Funcionário</h3> <input type="hidden" id="func-id"> <div class="form-group"><label for="func-nome-modal">Nome:</label> <input type="text" id="func-nome-modal"></div> <div class="form-group"><label for="func-setor-modal">Setor:</label> <input type="text" id="func-setor-modal"></div> <div class="modal-buttons"> <button class="btn-secondary" id="cancelFuncionarioButton"><i data-lucide="x"></i>Cancelar</button> <button class="btn-primary" id="saveFuncionarioButton"><i data-lucide="save"></i>Salvar</button> </div> </div> </div> <div id="produtoFormModal" class="modal"> <div class="modal-content form-modal"> <h3 id="produtoModalTitle">Cadastrar Produto</h3> <input type="hidden" id="prod-id"> <div class="form-group"><label for="prod-nome-modal">Nome:</label> <input type="text" id="prod-nome-modal"></div> <div class="form-group"><label for="prod-valor-modal">Valor:</label> <input type="number" step="0.01" id="prod-valor-modal"></div> <div class="modal-buttons"> <button class="btn-secondary" id="cancelProdutoButton"><i data-lucide="x"></i>Cancelar</button> <button class="btn-primary" id="saveProdutoButton"><i data-lucide="save"></i>Salvar</button> </div> </div> </div> <div id="lancamentoFormModal" class="modal"> <div class="modal-content form-modal"> <button class="close-modal-btn" onclick="closeLancamentoModal()"><i data-lucide="x"></i></button> <div class="modal-header"> <h3 id="lancamentoModalTitle">Lançar Consumo</h3> </div> <input type="hidden" id="cons-id"> <div class="form-group"> <label for="cons-func-modal">Funcionário:</label> <select id="cons-func-modal" style="width: 100%;"></select> </div> <div class="form-group"> <label for="cons-prod-modal">Produto:</label> <select id="cons-prod-modal" style="width: 100%;"></select> </div> <div class="form-row"> <div class="form-group"> <label for="cons-qtd-modal">Quantidade:</label> <div class="quantity-input"> <button type="button" class="quantity-btn minus">-</button> <input type="number" id="cons-qtd-modal" value="1" min="1"> <button type="button" class="quantity-btn plus">+</button> </div> </div> <div class="form-group"> <label for="cons-data-modal">Data:</label> <input type="date" id="cons-data-modal"> </div> </div> <div class="form-group"> <label for="cons-obs-modal">Observação:</label> <input type="text" id="cons-obs-modal" placeholder="Ex: Café da manhã"> </div> <div class="modal-buttons"> <button class="btn-secondary" id="cancelLancamentoButton"><i data-lucide="x"></i>Cancelar</button> <button class="btn-primary" id="saveLancamentoButton"><i data-lucide="check"></i>Lançar</button> </div> </div> </div> <footer> <p>By: JhonnyDarks - v3.0</p> </footer> </body> <script> // --- Variáveis Globais --- let db; let SQL; let currentConfirmAction = null; let mainDashboardChart = null; let currentPasswordCallback = null; let ADMIN_PASSWORD; // Não é mais uma const, será carregada/salva no localStorage // --- Funções de UI e Animação --- function showLoading() { document.getElementById('loading-overlay').style.display = 'flex'; } function hideLoading() { document.getElementById('loading-overlay').style.display = 'none'; } function applyTheme(theme) { document.documentElement.setAttribute('data-theme', theme); localStorage.setItem('theme', theme); const themeToggleBtn = document.getElementById('theme-toggle-btn'); if (themeToggleBtn) { const isDark = theme === 'dark'; const icon = isDark ? 'sun' : 'moon'; const text = isDark ? 'Mudar para Tema Claro' : 'Mudar para Tema Escuro'; themeToggleBtn.innerHTML = `<i data-lucide="${icon}"></i> ${text}`; lucide.createIcons(); } } function getFormattedDate(date) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } function showTab(index) { document.querySelectorAll('.section').forEach((sec, i) => sec.classList.toggle('active', i === index)); document.querySelectorAll('.tab-button').forEach((tab, i) => tab.classList.toggle('active', i === index)); if (index === 0) renderDashboard(); else if (index === 1) listarFuncionarios(); else if (index === 2) listarProdutos(); else if (index === 3) { setLancamentoDateFiltersToCurrentMonth(); listarLancamentos(true); } else if (index === 4) { setDataHoje(); atualizarCombos(); } else if (index === 5) { lucide.createIcons(); } // Garante que os ícones da aba de configurações sejam atualizados } // --- Script Completo e Funcional --- function setLancamentoDateFiltersToCurrentMonth() { const hoje = new Date(); const primeiroDia = new Date(hoje.getFullYear(), hoje.getMonth(), 1); const ultimoDia = new Date(hoje.getFullYear(), hoje.getMonth() + 1, 0); document.getElementById('lanc-filtro-de').value = getFormattedDate(primeiroDia); document.getElementById('lanc-filtro-ate').value = getFormattedDate(ultimoDia); } function goToPreviousMonth() { // NOVA FUNÇÃO: Navega para o mês anterior nos filtros de lançamento const inputDe = document.getElementById('lanc-filtro-de'); const inputAte = document.getElementById('lanc-filtro-ate'); let dataAtualDe = new Date(inputDe.value + 'T00:00:00'); // Calcula o primeiro dia do mês anterior ao mês da data de início atual let primeiroDiaMesAnterior = new Date(dataAtualDe.getFullYear(), dataAtualDe.getMonth() - 1, 1); // Calcula o último dia do mês anterior ao mês da data de início atual let ultimoDiaMesAnterior = new Date(primeiroDiaMesAnterior.getFullYear(), primeiroDiaMesAnterior.getMonth() + 1, 0); inputDe.value = getFormattedDate(primeiroDiaMesAnterior); inputAte.value = getFormattedDate(ultimoDiaMesAnterior); listarLancamentos(true); // Chama a função para listar com os novos filtros de data } function showToast(msg) { const toast = document.getElementById('toast'); toast.textContent = msg; toast.classList.add('show'); setTimeout(() => toast.classList.remove('show'), 3000); } function getDataLocal() { const hoje = new Date(); const ano = hoje.getFullYear(); const mes = String(hoje.getMonth() + 1).padStart(2, '0'); const dia = String(hoje.getDate()).padStart(2, '0'); return `${ano}-${mes}-${dia}`; } function setDataHoje() { const hoje = getDataLocal(); document.getElementById('filtro-de').value = hoje; document.getElementById('filtro-ate').value = hoje; } function showConfirmationModal(title, message, onConfirm, requiresPassword = false) { const modal = document.getElementById('confirmationModal'); document.getElementById('modalTitle').textContent = title; document.getElementById('modalMessage').textContent = message; if (requiresPassword) { document.getElementById('confirmButton').onclick = () => { document.getElementById('confirmationModal').style.display = 'none'; openPasswordModal(onConfirm); }; } else { document.getElementById('confirmButton').onclick = () => { if (onConfirm) onConfirm(); document.getElementById('confirmationModal').style.display = 'none'; currentConfirmAction = null; }; } modal.style.display = 'flex'; document.getElementById('cancelButton').onclick = () => { document.getElementById('confirmationModal').style.display = 'none'; currentConfirmAction = null; }; } function openPasswordModal(callback) { document.getElementById('admin-password').value = ''; // Limpa o campo da senha document.getElementById('passwordModal').style.display = 'flex'; currentPasswordCallback = callback; // Armazena a função de callback } function closePasswordModal() { document.getElementById('passwordModal').style.display = 'none'; currentPasswordCallback = null; } function openChangePasswordModal() { document.getElementById('current-admin-password').value = ''; document.getElementById('new-admin-password').value = ''; document.getElementById('confirm-new-admin-password').value = ''; document.getElementById('changePasswordModal').style.display = 'flex'; // Recria os ícones de olho ao abrir a modal lucide.createIcons(); } function closeChangePasswordModal() { document.getElementById('changePasswordModal').style.display = 'none'; } // Função para alternar a visibilidade da senha function togglePasswordVisibility(inputId, iconElement) { const input = document.getElementById(inputId); if (input.type === "password") { input.type = "text"; iconElement.setAttribute('data-lucide', 'eye-off'); // Muda para ícone de olho cortado } else { input.type = "password"; iconElement.setAttribute('data-lucide', 'eye'); // Muda de volta para ícone de olho } // Para recriar o ícone visualmente após a mudança de data-lucide lucide.createIcons(); } function openAddFuncionarioModal() { document.getElementById('funcionarioModalTitle').textContent = 'Cadastrar Funcionário'; document.getElementById('func-id').value = ''; document.getElementById('func-nome-modal').value = ''; document.getElementById('func-setor-modal').value = ''; document.getElementById('funcionarioFormModal').style.display = 'flex'; } function editFuncionario(id, nome, setor) { document.getElementById('funcionarioModalTitle').textContent = 'Editar Funcionário'; document.getElementById('func-id').value = id; document.getElementById('func-nome-modal').value = nome; document.getElementById('func-setor-modal').value = setor; document.getElementById('funcionarioFormModal').style.display = 'flex'; } function openAddProdutoModal() { document.getElementById('produtoModalTitle').textContent = 'Cadastrar Produto'; document.getElementById('prod-id').value = ''; document.getElementById('prod-nome-modal').value = ''; document.getElementById('prod-valor-modal').value = ''; document.getElementById('produtoFormModal').style.display = 'flex'; } function editProduto(id, nome, valor) { document.getElementById('produtoModalTitle').textContent = 'Editar Produto'; document.getElementById('prod-id').value = id; document.getElementById('prod-nome-modal').value = nome; document.getElementById('prod-valor-modal').value = valor; document.getElementById('produtoFormModal').style.display = 'flex'; } function openLancamentoModal() { const modal = document.getElementById('lancamentoFormModal'); document.getElementById('lancamentoModalTitle').textContent = 'Lançar Consumo'; document.getElementById('cons-id').value = ''; document.getElementById('cons-qtd-modal').value = '1'; document.getElementById('cons-obs-modal').value = ''; document.getElementById('cons-data-modal').value = getDataLocal(); atualizarCombosLancamentoModal(); modal.style.display = 'flex'; $('#cons-func-modal').select2({ placeholder: 'Selecione um funcionário...', dropdownParent: $('#lancamentoFormModal .modal-content') }); $('#cons-prod-modal').select2({ placeholder: 'Selecione um produto...', dropdownParent: $('#lancamentoFormModal .modal-content') }); } function closeLancamentoModal() { if ($('#cons-func-modal').data('select2')) $('#cons-func-modal').select2('destroy'); if ($('#cons-prod-modal').data('select2')) $('#cons-prod-modal').select2('destroy'); document.getElementById('lancamentoFormModal').style.display = 'none'; } function atualizarCombosLancamentoModal() { const funcs = db.exec("SELECT id, nome FROM funcionarios WHERE ativo = 1 ORDER BY nome")[0]?.values || []; const prods = db.exec("SELECT id, nome FROM produtos WHERE ativo = 1 ORDER BY nome")[0]?.values || []; const selFunc = $('#cons-func-modal'); const selProd = $('#cons-prod-modal'); selFunc.empty().append(new Option('', '', true, true)); selProd.empty().append(new Option('', '', true, true)); funcs.forEach(([id, nome]) => selFunc.append(new Option(nome, id, false, false))); prods.forEach(([id, nome]) => selProd.append(new Option(nome, id, false, false))); selFunc.trigger('change'); selProd.trigger('change'); } document.addEventListener('click', function(e) { if (e.target.closest('.quantity-btn')) { const button = e.target.closest('.quantity-btn'); const wrapper = button.closest('.quantity-input'); const input = wrapper.querySelector('input[type="number"]'); if (!input) return; let currentValue = parseInt(input.value, 10); const min = parseInt(input.min, 10); if (button.classList.contains('plus')) currentValue++; else if (button.classList.contains('minus')) currentValue--; if (!isNaN(min) && currentValue < min) currentValue = min; input.value = currentValue; } }); function checkAndUpdateSchema() { try { db.exec("SELECT ativo FROM funcionarios LIMIT 1"); } catch (e) { db.run("ALTER TABLE funcionarios ADD COLUMN ativo INTEGER DEFAULT 1"); db.run("UPDATE funcionarios SET ativo = 1 WHERE ativo IS NULL"); } try { db.exec("SELECT ativo FROM produtos LIMIT 1"); } catch (e) { db.run("ALTER TABLE produtos ADD COLUMN ativo INTEGER DEFAULT 1"); db.run("UPDATE produtos SET ativo = 1 WHERE ativo IS NULL"); } salvarBanco(); } function salvarBanco() { const data = db.export(); const b64 = btoa(String.fromCharCode(...data)); localStorage.setItem('db', b64); } function atualizarCombos() { const funcs = db.exec("SELECT id, nome FROM funcionarios WHERE ativo = 1 ORDER BY nome")[0]?.values || []; const selFuncFiltro = document.getElementById('filtro-func'); if (selFuncFiltro) { selFuncFiltro.innerHTML = '<option value="">Todos os Funcionários</option>' + funcs.map(([id, nome]) => `<option value="${id}">${nome}</option>`).join(''); } } function listarFuncionarios() { showLoading(); setTimeout(() => { const result = db.exec("SELECT id, nome, setor, ativo FROM funcionarios ORDER BY nome")[0]; const container = document.getElementById('lista-funcionarios'); if (!result || !result.values.length) { container.innerHTML = '<p>Nenhum funcionário cadastrado.</p>'; hideLoading(); return; } const html = `<h3>Lista de Funcionários</h3><table><tr><th>ID</th><th>Nome</th><th>Setor</th><th>Status</th><th>Ações</th></tr>${result.values.map(r => { const [id, nome, setor, ativo] = r; const toggleButtonClass = ativo === 1 ? 'toggle-btn' : 'activate-btn'; const toggleIcon = ativo ? 'toggle-left' : 'toggle-right'; return `<tr class="${!ativo ? 'inactive-row' : ''}"> <td>${id}</td> <td>${nome}</td> <td>${setor}</td> <td>${ativo ? 'Ativo' : 'Inativo'}</td> <td> <button class="edit-btn" title="Editar" onclick="editFuncionario(${id}, '${nome.replace(/'/g, "\\'")}', '${setor.replace(/'/g, "\\'")}')"><i data-lucide="edit"></i></button> <button class="${toggleButtonClass}" title="${ativo ? 'Desativar' : 'Ativar'}" onclick="toggleFuncionarioStatus(${id}, ${ativo})"><i data-lucide="${toggleIcon}"></i></button> <button class="delete-btn" title="Excluir" onclick="showConfirmationModal('Confirmar Exclusão', 'Tem certeza? Lançamentos associados serão perdidos.', () => { db.run('DELETE FROM consumo WHERE funcionario_id = ?', [${id}]); db.run('DELETE FROM funcionarios WHERE id = ?', [${id}]); salvarBanco(); atualizarCombosLancamentoModal(); atualizarCombos(); listarFuncionarios(); listarLancamentos(true); showToast('Funcionário excluído.'); }, true)"><i data-lucide="trash-2"></i></button> </td> </tr>`; }).join('')}</table>`; container.innerHTML = html; lucide.createIcons(); hideLoading(); }, 50); } function toggleFuncionarioStatus(id, currentStatus) { const newStatus = 1 - currentStatus; db.run("UPDATE funcionarios SET ativo = ? WHERE id = ?", [newStatus, id]); salvarBanco(); showToast(`Funcionário ${newStatus === 1 ? 'ativado' : 'desativado'}.`); listarFuncionarios(); atualizarCombos(); atualizarCombosLancamentoModal(); } function listarProdutos() { showLoading(); setTimeout(() => { const result = db.exec("SELECT id, nome, valor, ativo FROM produtos ORDER BY nome")[0]; const container = document.getElementById('lista-produtos'); if (!result || !result.values.length) { container.innerHTML = '<p>Nenhum produto cadastrado.</p>'; hideLoading(); return; } const html = `<h3>Lista de Produtos</h3><table><tr><th>ID</th><th>Nome</th><th>Valor</th><th>Status</th><th>Ações</th></tr>${result.values.map(r => { const [id, nome, valor, ativo] = r; const toggleButtonClass = ativo === 1 ? 'toggle-btn' : 'activate-btn'; const toggleIcon = ativo ? 'toggle-left' : 'toggle-right'; return `<tr class="${!ativo ? 'inactive-row' : ''}"> <td>${id}</td> <td>${nome}</td> <td>R$ ${valor.toFixed(2)}</td> <td>${ativo ? 'Ativo' : 'Inativo'}</td> <td> <button class="edit-btn" title="Editar" onclick="editProduto(${id}, '${nome.replace(/'/g, "\\'")}', ${valor})"><i data-lucide="edit"></i></button> <button class="${toggleButtonClass}" title="${ativo ? 'Desativar' : 'Ativar'}" onclick="toggleProdutoStatus(${id}, ${ativo})"><i data-lucide="${toggleIcon}"></i></button> <button class="delete-btn" title="Excluir" onclick="showConfirmationModal('Confirmar Exclusão', 'Tem certeza? Lançamentos associados serão perdidos.', () => { db.run('DELETE FROM consumo WHERE produto_id = ?', [${id}]); db.run('DELETE FROM produtos WHERE id = ?', [${id}]); salvarBanco(); atualizarCombosLancamentoModal(); atualizarCombos(); listarProdutos(); listarLancamentos(true); showToast('Produto excluído.'); }, true)"><i data-lucide="trash-2"></i></button> </td> </tr>`; }).join('')}</table>`; container.innerHTML = html; lucide.createIcons(); hideLoading(); }, 50); } function toggleProdutoStatus(id, currentStatus) { const newStatus = 1 - currentStatus; db.run("UPDATE produtos SET ativo = ? WHERE id = ?", [newStatus, id]); salvarBanco(); showToast(`Produto ${newStatus === 1 ? 'ativado' : 'desativado'}.`); listarProdutos(); atualizarCombos(); atualizarCombosLancamentoModal(); } function listarLancamentos(filtrarPorData = false) { showLoading(); setTimeout(() => { const pesquisa = document.getElementById('lanc-pesquisa').value.toLowerCase(); const container = document.getElementById('lista-lancamentos'); let query = `SELECT c.id, f.nome, p.nome, c.quantidade, c.data, c.obs, p.valor FROM consumo c JOIN funcionarios f ON f.id = c.funcionario_id JOIN produtos p ON p.id = c.produto_id`; let params = []; if (filtrarPorData) { const de = document.getElementById('lanc-filtro-de').value; const ate = document.getElementById('lanc-filtro-ate').value; if (de && ate) { query += ' WHERE c.data BETWEEN ? AND ?'; params.push(de, ate); } } query += ' ORDER BY c.data DESC, c.id DESC'; try { const result = db.exec(query, params)[0]; if (!result || result.values.length === 0) { container.innerHTML = `<p>Nenhum lançamento encontrado.</p>`; hideLoading(); return; } const filteredRows = result.values.filter(r => (r[1] && r[1].toLowerCase().includes(pesquisa)) || (r[2] && r[2].toLowerCase().includes(pesquisa)) || (r[5] && r[5].toLowerCase().includes(pesquisa)) || (r[4] && r[4].toLowerCase().includes(pesquisa)) ); if (filteredRows.length === 0) { container.innerHTML = '<p>Nenhum lançamento com a pesquisa atual.</p>'; hideLoading(); return; } const html = `<h3>Lançamentos</h3><table><tr><th>ID</th><th>Funcionário</th><th>Produto</th><th>Qtd</th><th>Data</th><th>Obs</th><th>Ações</th></tr>${filteredRows.map(r => `<tr><td>${r[0]}</td><td>${r[1]}</td><td>${r[2]}</td><td>${r[3]}</td><td>${new Date(r[4] + 'T00:00:00').toLocaleDateString()}</td><td>${r[5] || ''}</td><td><button class="delete-btn" title="Excluir" onclick="showConfirmationModal('Confirmar Exclusão', 'Excluir este lançamento?', () => { db.run('DELETE FROM consumo WHERE id = ?', [${r[0]}]); salvarBanco(); listarLancamentos(true); showToast('Lançamento excluído.'); }, true)"><i data-lucide="trash-2"></i></button></td></tr>`).join('')}</table>`; container.innerHTML = html; lucide.createIcons(); } catch (e) { console.error("Erro ao listar lançamentos:", e); container.innerHTML = `<p style="color:red;">Erro ao carregar os lançamentos. Verifique o console.</p>`; } finally { hideLoading(); } }, 50); } function resetarFiltrosLancamentos() { setLancamentoDateFiltersToCurrentMonth(); document.getElementById('lanc-pesquisa').value = ''; listarLancamentos(true); } function renderDashboard() { showLoading(); setTimeout(() => { const hoje = new Date(); const primeiroDia = getFormattedDate(new Date(hoje.getFullYear(), hoje.getMonth(), 1)); const ultimoDia = getFormattedDate(new Date(hoje.getFullYear(), hoje.getMonth() + 1, 0)); let totalMes = 0; const totalQuery = db.exec(`SELECT SUM(p.valor * c.quantidade) FROM consumo c JOIN produtos p ON p.id = c.produto_id WHERE c.data BETWEEN '${primeiroDia}' AND '${ultimoDia}'`)[0]; if (totalQuery && totalQuery.values[0][0]) { totalMes = totalQuery.values[0][0]; } document.querySelector('#card-total-mes .value').textContent = `R$ ${totalMes.toFixed(2)}`; let topProduto = '-'; const topProdQuery = db.exec(`SELECT p.nome FROM consumo c JOIN produtos p ON p.id = c.produto_id WHERE c.data BETWEEN '${primeiroDia}' AND '${ultimoDia}' GROUP BY p.nome ORDER BY SUM(c.quantidade) DESC LIMIT 1`)[0]; if (topProdQuery && topProdQuery.values.length > 0) { topProduto = topProdQuery.values[0][0]; } document.querySelector('#card-top-produto .value').textContent = topProduto; let topConsumidor = '-'; const topConsumidorQuery = db.exec(`SELECT f.nome FROM consumo c JOIN funcionarios f ON f.id = c.funcionario_id JOIN produtos p ON p.id = c.produto_id WHERE c.data BETWEEN '${primeiroDia}' AND '${ultimoDia}' GROUP BY f.nome ORDER BY SUM(p.valor * c.quantidade) DESC LIMIT 1`)[0]; if (topConsumidorQuery && topConsumidorQuery.values.length > 0) { topConsumidor = topConsumidorQuery.values[0][0]; } document.querySelector('#card-top-consumidor .value').textContent = topConsumidor; const chartDataQuery = db.exec(`SELECT f.nome, SUM(p.valor * c.quantidade) as total FROM consumo c JOIN funcionarios f ON f.id = c.funcionario_id JOIN produtos p ON p.id = c.produto_id WHERE c.data BETWEEN '${primeiroDia}' AND '${ultimoDia}' GROUP BY f.nome ORDER BY total DESC LIMIT 5`)[0]; if (mainDashboardChart) mainDashboardChart.destroy(); const ctx = document.getElementById('mainDashboardChart').getContext('2d'); if (chartDataQuery && chartDataQuery.values.length > 0) { const labels = chartDataQuery.values.map(row => row[0]); const data = chartDataQuery.values.map(row => row[1]); mainDashboardChart = new Chart(ctx, { type: 'bar', data: { labels, datasets: [{ label: 'Top 5 Consumidores do Mês (R$)', data, backgroundColor: 'rgba(76, 175, 80, 0.5)', borderColor: 'rgba(76, 175, 80, 1)', borderWidth: 1 }] }, options: { scales: { y: { beginAtZero: true } }, responsive: true, maintainAspectRatio: false } }); } else { // Clear the canvas if no data to display ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // Optionally display a message on the chart area ctx.font = "16px Inter"; ctx.fillStyle = "var(--light-text-color)"; ctx.textAlign = "center"; ctx.fillText("Nenhum dado de consumo para este mês.", ctx.canvas.width / 2, ctx.canvas.height / 2); } hideLoading(); lucide.createIcons(); }, 50); } function gerarRelatorio() { showLoading(); setTimeout(() => { const funcId = document.getElementById('filtro-func').value; const de = document.getElementById('filtro-de').value; const ate = document.getElementById('filtro-ate').value; if (!de || !ate) { showToast('Selecione as datas.'); hideLoading(); return; } const stmt = ` SELECT f.nome, p.nome, c.quantidade, p.valor, (c.quantidade * p.valor) AS total, c.data FROM consumo c JOIN funcionarios f ON f.id = c.funcionario_id JOIN produtos p ON p.id = c.produto_id WHERE c.data BETWEEN ? AND ? ${funcId ? 'AND f.id = ?' : ''} ORDER BY c.data `; const params = funcId ? [de, ate, funcId] : [de, ate]; const container = document.getElementById('relatorio'); try { const result = db.exec(stmt, params)[0]; if (!result || result.values.length === 0) { container.innerHTML = `<div class="empty-state"><i data-lucide="info"></i><h3>Nenhum registro encontrado</h3><p>Não há dados para os filtros selecionados.</p></div>`; lucide.createIcons(); hideLoading(); return; } let totalGeral = 0; const rows = result.values.map(r => { totalGeral += r[4]; // r[4] é o total (quantidade * valor) return `<tr> <td>${r[0]}</td> <td>${r[1]}</td> <td>${r[2]}</td> <td>R$ ${r[3].toFixed(2)}</td> <td>R$ ${r[4].toFixed(2)}</td> <td>${new Date(r[5] + 'T00:00:00').toLocaleDateString()}</td> </tr>`; }).join(''); const selectedFuncName = funcId ? db.exec(`SELECT nome FROM funcionarios WHERE id = ${funcId}`)[0]?.values[0][0] : 'Todos'; const reportTitle = `Relatório de Consumo (De: ${de} a ${ate}${funcId ? `, ${selectedFuncName}` : ''})`; container.innerHTML = ` <h3>${reportTitle}</h3> <table> <thead> <tr> <th>Funcionário</th> <th>Produto</th> <th>Qtd</th> <th>Valor Unit.</th> <th>Total</th> <th>Data</th> </tr> </thead> <tbody> ${rows} </tbody> <tfoot> <tr> <td colspan="4" style="text-align:right"><strong>Total:</strong></td> <td colspan="2"><strong>R$ ${totalGeral.toFixed(2)}</strong></td> </tr> </tfoot> </table> `; } catch(e) { console.error("Erro ao gerar relatório: ", e); container.innerHTML = `<div class="empty-state"><i data-lucide="alert-triangle"></i><h3>Ocorreu um erro</h3><p>Não foi possível gerar o relatório. Verifique o console.</p></div>`; lucide.createIcons(); } finally { hideLoading(); } }, 50); } async function imprimirRelatorio() { const reportElement = document.getElementById('relatorio'); if (!reportElement.querySelector('table')) { showToast("Gere um relatório antes de exportar para PDF."); return; } showLoading(); showToast("Gerando PDF, aguarde..."); // Small delay to ensure loading indicator is visible await new Promise(resolve => setTimeout(resolve, 100)); try { const tableElement = reportElement.querySelector('table'); // Temporarily set overflow to visible for html2canvas to capture full content const originalOverflow = tableElement.style.overflow; tableElement.style.overflow = 'visible'; const canvas = await html2canvas(tableElement, { scale: 2, // Aumenta a escala para melhor qualidade no PDF useCORS: true, logging: false, // ignoreElements: (element) => { // // Ignora elementos que não devem aparecer no PDF, se houver // // return element.classList.contains('no-print'); // return false; // } }); // Restore original overflow tableElement.style.overflow = originalOverflow; const imgData = canvas.toDataURL('image/png'); const { jsPDF } = window.jspdf; const pdf = new jsPDF('p', 'mm', 'a4'); // 'p' for portrait, 'mm' for millimeters, 'a4' for A4 size const imgProps = pdf.getImageProperties(imgData); const pdfWidth = pdf.internal.pageSize.getWidth() - 20; // 10mm margin on each side const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width; // Add title to PDF pdf.text(document.querySelector('#relatorio h3').textContent, 10, 10); // Add image to PDF, starting at Y=20 (after title) pdf.addImage(imgData, 'PNG', 10, 20, pdfWidth, pdfHeight); pdf.save(`relatorio_consumo_${new Date().toISOString().slice(0,10)}.pdf`); showToast("PDF gerado com sucesso!"); } catch (e) { console.error("Erro ao gerar PDF:", e); showToast("Erro ao gerar o PDF. Verifique o console."); } finally { hideLoading(); } } function exportarDB() { const data = db.export(); const blob = new Blob([data], { type: 'application/octet-stream' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `backup_consumo_${new Date().toISOString().slice(0,10)}.sqlite`; a.click(); URL.revokeObjectURL(url); showToast('Banco de dados exportado.'); } function importarDB(event) { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function (e) { try { const Uints = new Uint8Array(e.target.result); db = new SQL.Database(Uints); checkAndUpdateSchema(); salvarBanco(); atualizarCombos(); showTab(0); // Volta para a dashboard após importar showToast('Banco de dados importado!'); } catch (error) { console.error("Erro ao importar banco:", error); showToast('Arquivo inválido ou corrompido.'); } }; reader.readAsArrayBuffer(file); } // --- Inicialização --- initSqlJs({ locateFile: file => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.6.2/${file}` }).then(sqlInstance => { SQL = sqlInstance; const savedDb = localStorage.getItem('db'); if (savedDb) { const Uints = Uint8Array.from(atob(savedDb), c => c.charCodeAt(0)); db = new SQL.Database(Uints); } else { db = new SQL.Database(); db.run(` CREATE TABLE funcionarios (id INTEGER PRIMARY KEY, nome TEXT, setor TEXT, ativo INTEGER DEFAULT 1); CREATE TABLE produtos (id INTEGER PRIMARY KEY, nome TEXT, valor REAL, ativo INTEGER DEFAULT 1); CREATE TABLE consumo (id INTEGER PRIMARY KEY, funcionario_id INTEGER, produto_id INTEGER, quantidade INTEGER, data TEXT, obs TEXT); `); } checkAndUpdateSchema(); // --- CARREGAR A SENHA DE ADMINISTRADOR NO INÍCIO --- // Se a senha não existe no localStorage, define uma padrão e salva. // CUIDADO: Em produção, essa senha padrão deveria ser definida de forma mais segura. ADMIN_PASSWORD = localStorage.getItem('admin_password') || 'admin123'; localStorage.setItem('admin_password', ADMIN_PASSWORD); // Garante que a senha padrão seja salva ou atualizada // Listeners // Removido o listener direto do confirmButton, agora ele é configurado dinamicamente no showConfirmationModal document.getElementById('cancelButton').addEventListener('click', () => { document.getElementById('confirmationModal').style.display = 'none'; currentConfirmAction = null; }); // Listener para o botão de confirmar senha do passwordModal document.getElementById('confirmPasswordButton').addEventListener('click', () => { const enteredPassword = document.getElementById('admin-password').value; if (enteredPassword === ADMIN_PASSWORD) { closePasswordModal(); if (currentPasswordCallback) { currentPasswordCallback(); // Executa a ação original após a senha correta showToast("Senha correta. Ação autorizada."); } } else { showToast("Senha incorreta!"); document.getElementById('admin-password').value = ''; // Limpa o campo } }); // Listener para o botão de salvar nova senha do changePasswordModal document.getElementById('saveNewPasswordButton').addEventListener('click', () => { const currentPassword = document.getElementById('current-admin-password').value; const newPassword = document.getElementById('new-admin-password').value; const confirmNewPassword = document.getElementById('confirm-new-admin-password').value; if (currentPassword !== ADMIN_PASSWORD) { showToast("Senha atual incorreta!"); return; } if (newPassword === '') { showToast("A nova senha não pode ser vazia."); return; } if (newPassword !== confirmNewPassword) { showToast("A nova senha e a confirmação não coincidem."); return; } ADMIN_PASSWORD = newPassword; // Atualiza a variável global localStorage.setItem('admin_password', ADMIN_PASSWORD); // Salva a nova senha no localStorage showToast("Senha de administrador alterada com sucesso!"); closeChangePasswordModal(); }); document.getElementById('saveFuncionarioButton').addEventListener('click', () => { const id = document.getElementById('func-id').value; const nome = document.getElementById('func-nome-modal').value.trim(); const setor = document.getElementById('func-setor-modal').value.trim(); if (!nome || !setor) { showToast('Preencha nome e setor.'); return; } if (id) { db.run(`UPDATE funcionarios SET nome = ?, setor = ? WHERE id = ?`, [nome, setor, id]); showToast('Funcionário atualizado!'); } else { db.run(`INSERT INTO funcionarios (nome, setor, ativo) VALUES (?, ?, 1)`, [nome, setor]); showToast("Funcionário cadastrado."); } salvarBanco(); listarFuncionarios(); atualizarCombosLancamentoModal(); atualizarCombos(); // Atualiza combos de filtro de relatório document.getElementById('funcionarioFormModal').style.display = 'none'; }); document.getElementById('cancelFuncionarioButton').addEventListener('click', () => document.getElementById('funcionarioFormModal').style.display = 'none'); document.getElementById('saveProdutoButton').addEventListener('click', () => { const id = document.getElementById('prod-id').value; const nome = document.getElementById('prod-nome-modal').value.trim(); const valor = parseFloat(document.getElementById('prod-valor-modal').value); if (!nome || isNaN(valor) || valor <= 0) { showToast('Preencha nome e valor válido.'); return; } if (id) { db.run(`UPDATE produtos SET nome = ?, valor = ? WHERE id = ?`, [nome, valor, id]); showToast('Produto atualizado!'); } else { db.run(`INSERT INTO produtos (nome, valor, ativo) VALUES (?, ?, 1)`, [nome, valor]); showToast("Produto cadastrado."); } salvarBanco(); listarProdutos(); atualizarCombosLancamentoModal(); atualizarCombos(); // Atualiza combos de filtro de relatório document.getElementById('produtoFormModal').style.display = 'none'; }); document.getElementById('cancelProdutoButton').addEventListener('click', () => document.getElementById('produtoFormModal').style.display = 'none'); document.getElementById('saveLancamentoButton').addEventListener('click', () => { const funcionario_id = $('#cons-func-modal').val(); const produto_id = $('#cons-prod-modal').val(); const qtd = parseInt(document.getElementById('cons-qtd-modal').value); const data = document.getElementById('cons-data-modal').value; const obs = document.getElementById('cons-obs-modal').value.trim(); if (!funcionario_id || !produto_id || isNaN(qtd) || qtd <= 0 || !data) { showToast('Preencha todos os campos corretamente.'); return; } db.run(`INSERT INTO consumo (funcionario_id, produto_id, quantidade, data, obs) VALUES (?, ?, ?, ?, ?)`, [funcionario_id, produto_id, qtd, data, obs]); showToast("Consumo lançado."); salvarBanco(); listarLancamentos(true); closeLancamentoModal(); renderDashboard(); // Atualiza o dashboard após novo lançamento }); document.getElementById('cancelLancamentoButton').addEventListener('click', () => closeLancamentoModal()); document.getElementById('theme-toggle-btn').addEventListener('click', () => { const newTheme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark'; applyTheme(newTheme); }); const currentTheme = localStorage.getItem('theme') || 'light'; applyTheme(currentTheme); showTab(0); // Mostra a aba Visão Geral ao iniciar lucide.createIcons(); // Inicializa os ícones do Lucide }); </script> </html> - Initial Deployment