|
|
<!DOCTYPE html> |
|
|
<html lang="pt-BR"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>PharmaManager - Sistema de Gerenciamento de Farmácia</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
<style> |
|
|
.sidebar { |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
.sidebar.collapsed { |
|
|
width: 70px; |
|
|
} |
|
|
.sidebar.collapsed .sidebar-text { |
|
|
display: none; |
|
|
} |
|
|
.sidebar.collapsed .logo-text { |
|
|
display: none; |
|
|
} |
|
|
.main-content { |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
.main-content.expanded { |
|
|
margin-left: 70px; |
|
|
} |
|
|
@media (max-width: 768px) { |
|
|
.sidebar { |
|
|
width: 0; |
|
|
overflow: hidden; |
|
|
} |
|
|
.sidebar.collapsed { |
|
|
width: 0; |
|
|
} |
|
|
.main-content { |
|
|
margin-left: 0; |
|
|
} |
|
|
.mobile-menu-btn { |
|
|
display: block; |
|
|
} |
|
|
} |
|
|
.toast { |
|
|
animation: fadeInOut 3s ease-in-out; |
|
|
} |
|
|
@keyframes fadeInOut { |
|
|
0% { opacity: 0; } |
|
|
10% { opacity: 1; } |
|
|
90% { opacity: 1; } |
|
|
100% { opacity: 0; } |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-100"> |
|
|
|
|
|
<button class="mobile-menu-btn fixed top-4 left-4 z-50 bg-blue-600 text-white p-2 rounded-md shadow-lg md:hidden"> |
|
|
<i class="fas fa-bars"></i> |
|
|
</button> |
|
|
|
|
|
|
|
|
<div class="sidebar fixed h-full bg-blue-800 text-white w-64 shadow-lg"> |
|
|
<div class="p-4 flex items-center"> |
|
|
<i class="fas fa-pills text-2xl mr-2"></i> |
|
|
<span class="logo-text text-xl font-bold">PharmaManager</span> |
|
|
</div> |
|
|
<nav class="mt-6"> |
|
|
<div class="px-4 py-2 hover:bg-blue-700 cursor-pointer flex items-center"> |
|
|
<i class="fas fa-tachometer-alt mr-3"></i> |
|
|
<span class="sidebar-text">Dashboard</span> |
|
|
</div> |
|
|
<div class="px-4 py-2 hover:bg-blue-700 cursor-pointer flex items-center" id="customer-nav"> |
|
|
<i class="fas fa-users mr-3"></i> |
|
|
<span class="sidebar-text">Clientes</span> |
|
|
</div> |
|
|
<div class="px-4 py-2 hover:bg-blue-700 cursor-pointer flex items-center" id="products-nav"> |
|
|
<i class="fas fa-boxes mr-3"></i> |
|
|
<span class="sidebar-text">Produtos</span> |
|
|
</div> |
|
|
<div class="px-4 py-2 hover:bg-blue-700 cursor-pointer flex items-center" id="sales-nav"> |
|
|
<i class="fas fa-shopping-cart mr-3"></i> |
|
|
<span class="sidebar-text">Vendas</span> |
|
|
</div> |
|
|
<div class="px-4 py-2 hover:bg-blue-700 cursor-pointer flex items-center" id="inventory-nav"> |
|
|
<i class="fas fa-warehouse mr-3"></i> |
|
|
<span class="sidebar-text">Estoque</span> |
|
|
</div> |
|
|
<div class="px-4 py-2 hover:bg-blue-700 cursor-pointer flex items-center" id="reports-nav"> |
|
|
<i class="fas fa-chart-bar mr-3"></i> |
|
|
<span class="sidebar-text">Relatórios</span> |
|
|
</div> |
|
|
<div class="px-4 py-2 hover:bg-blue-700 cursor-pointer flex items-center" id="settings-nav"> |
|
|
<i class="fas fa-cog mr-3"></i> |
|
|
<span class="sidebar-text">Configurações</span> |
|
|
</div> |
|
|
<div class="px-4 py-2 hover:bg-blue-700 cursor-pointer flex items-center" id="backup-nav"> |
|
|
<i class="fas fa-database mr-3"></i> |
|
|
<span class="sidebar-text">Backup/Restore</span> |
|
|
</div> |
|
|
</nav> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="main-content ml-64 min-h-screen"> |
|
|
|
|
|
<header class="bg-white shadow-sm p-4 flex justify-between items-center"> |
|
|
<h1 class="text-2xl font-semibold text-gray-800" id="page-title">Dashboard</h1> |
|
|
<div class="flex items-center space-x-4"> |
|
|
<div class="relative"> |
|
|
<i class="fas fa-bell text-gray-600 cursor-pointer hover:text-blue-600"></i> |
|
|
<span class="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center">3</span> |
|
|
</div> |
|
|
<div class="flex items-center space-x-2"> |
|
|
<img src="https://via.placeholder.com/40" alt="User" class="rounded-full"> |
|
|
<span class="text-gray-700">Admin</span> |
|
|
</div> |
|
|
</div> |
|
|
</header> |
|
|
|
|
|
|
|
|
<div class="p-6"> |
|
|
|
|
|
<div id="dashboard-content"> |
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6"> |
|
|
<div class="bg-white p-6 rounded-lg shadow-md"> |
|
|
<div class="flex justify-between items-center"> |
|
|
<div> |
|
|
<p class="text-gray-500">Clientes Cadastrados</p> |
|
|
<h3 class="text-2xl font-bold" id="customer-count">0</h3> |
|
|
</div> |
|
|
<div class="bg-blue-100 p-3 rounded-full"> |
|
|
<i class="fas fa-users text-blue-600"></i> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="bg-white p-6 rounded-lg shadow-md"> |
|
|
<div class="flex justify-between items-center"> |
|
|
<div> |
|
|
<p class="text-gray-500">Produtos em Estoque</p> |
|
|
<h3 class="text-2xl font-bold" id="product-count">0</h3> |
|
|
</div> |
|
|
<div class="bg-green-100 p-3 rounded-full"> |
|
|
<i class="fas fa-boxes text-green-600"></i> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="bg-white p-6 rounded-lg shadow-md"> |
|
|
<div class="flex justify-between items-center"> |
|
|
<div> |
|
|
<p class="text-gray-500">Vendas Hoje</p> |
|
|
<h3 class="text-2xl font-bold" id="sales-today">0</h3> |
|
|
</div> |
|
|
<div class="bg-yellow-100 p-3 rounded-full"> |
|
|
<i class="fas fa-shopping-cart text-yellow-600"></i> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="bg-white p-6 rounded-lg shadow-md"> |
|
|
<div class="flex justify-between items-center"> |
|
|
<div> |
|
|
<p class="text-gray-500">Receita Mensal</p> |
|
|
<h3 class="text-2xl font-bold" id="monthly-revenue">R$ 0,00</h3> |
|
|
</div> |
|
|
<div class="bg-purple-100 p-3 rounded-full"> |
|
|
<i class="fas fa-money-bill-wave text-purple-600"></i> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> |
|
|
<div class="bg-white p-6 rounded-lg shadow-md lg:col-span-2"> |
|
|
<h2 class="text-xl font-semibold mb-4">Vendas Recentes</h2> |
|
|
<div class="overflow-x-auto"> |
|
|
<table class="min-w-full divide-y divide-gray-200"> |
|
|
<thead class="bg-gray-50"> |
|
|
<tr> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Cliente</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Data</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Total</th> |
|
|
</tr> |
|
|
</thead> |
|
|
<tbody class="bg-white divide-y divide-gray-200" id="recent-sales"> |
|
|
<tr> |
|
|
<td colspan="4" class="px-6 py-4 text-center text-gray-500">Nenhuma venda recente</td> |
|
|
</tr> |
|
|
</tbody> |
|
|
</table> |
|
|
</div> |
|
|
</div> |
|
|
<div class="bg-white p-6 rounded-lg shadow-md"> |
|
|
<h2 class="text-xl font-semibold mb-4">Estoque Baixo</h2> |
|
|
<div class="space-y-4" id="low-stock"> |
|
|
<div class="text-center text-gray-500">Nenhum produto com estoque baixo</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="customer-content" class="hidden"> |
|
|
<div class="flex justify-between items-center mb-6"> |
|
|
<h2 class="text-2xl font-semibold">Gerenciamento de Clientes</h2> |
|
|
<button id="add-customer-btn" class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 flex items-center"> |
|
|
<i class="fas fa-plus mr-2"></i> Novo Cliente |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div class="bg-white rounded-lg shadow-md overflow-hidden"> |
|
|
<div class="p-4 border-b flex justify-between items-center"> |
|
|
<div class="relative w-64"> |
|
|
<input type="text" id="customer-search" placeholder="Buscar clientes..." class="w-full pl-10 pr-4 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
|
<i class="fas fa-search absolute left-3 top-3 text-gray-400"></i> |
|
|
</div> |
|
|
<div class="flex space-x-2"> |
|
|
<button id="export-customers" class="bg-green-600 text-white px-3 py-1 rounded-md hover:bg-green-700 flex items-center"> |
|
|
<i class="fas fa-file-export mr-1"></i> Exportar |
|
|
</button> |
|
|
<button id="import-customers" class="bg-purple-600 text-white px-3 py-1 rounded-md hover:bg-purple-700 flex items-center"> |
|
|
<i class="fas fa-file-import mr-1"></i> Importar |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
<div class="overflow-x-auto"> |
|
|
<table class="min-w-full divide-y divide-gray-200"> |
|
|
<thead class="bg-gray-50"> |
|
|
<tr> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Nome</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">CPF</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Telefone</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Ações</th> |
|
|
</tr> |
|
|
</thead> |
|
|
<tbody class="bg-white divide-y divide-gray-200" id="customer-table-body"> |
|
|
|
|
|
</tbody> |
|
|
</table> |
|
|
</div> |
|
|
<div class="p-4 border-t flex justify-between items-center"> |
|
|
<div class="text-sm text-gray-500"> |
|
|
Mostrando <span id="customer-start">1</span> a <span id="customer-end">10</span> de <span id="customer-total">0</span> clientes |
|
|
</div> |
|
|
<div class="flex space-x-2"> |
|
|
<button id="customer-prev" class="px-3 py-1 border rounded-md disabled:opacity-50" disabled>Anterior</button> |
|
|
<button id="customer-next" class="px-3 py-1 border rounded-md disabled:opacity-50" disabled>Próximo</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="backup-content" class="hidden"> |
|
|
<div class="flex justify-between items-center mb-6"> |
|
|
<h2 class="text-2xl font-semibold">Backup e Restauração</h2> |
|
|
</div> |
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6"> |
|
|
<div class="bg-white p-6 rounded-lg shadow-md"> |
|
|
<h3 class="text-xl font-semibold mb-4 flex items-center"> |
|
|
<i class="fas fa-download mr-2 text-blue-600"></i> Criar Backup |
|
|
</h3> |
|
|
<p class="text-gray-600 mb-4">Crie um backup completo do banco de dados para restaurar posteriormente.</p> |
|
|
<button id="create-backup-btn" class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 flex items-center w-full justify-center"> |
|
|
<i class="fas fa-save mr-2"></i> Criar Backup |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div class="bg-white p-6 rounded-lg shadow-md"> |
|
|
<h3 class="text-xl font-semibold mb-4 flex items-center"> |
|
|
<i class="fas fa-upload mr-2 text-green-600"></i> Restaurar Backup |
|
|
</h3> |
|
|
<p class="text-gray-600 mb-4">Restaurar o banco de dados a partir de um arquivo de backup.</p> |
|
|
<div class="mb-4"> |
|
|
<label class="block text-gray-700 mb-2">Selecione o arquivo de backup:</label> |
|
|
<input type="file" id="restore-file" class="block w-full text-sm text-gray-500 |
|
|
file:mr-4 file:py-2 file:px-4 |
|
|
file:rounded-md file:border-0 |
|
|
file:text-sm file:font-semibold |
|
|
file:bg-blue-50 file:text-blue-700 |
|
|
hover:file:bg-blue-100"> |
|
|
</div> |
|
|
<button id="restore-backup-btn" class="bg-green-600 text-white px-4 py-2 rounded-md hover:bg-green-700 flex items-center w-full justify-center"> |
|
|
<i class="fas fa-redo mr-2"></i> Restaurar Backup |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="bg-white p-6 rounded-lg shadow-md mt-6"> |
|
|
<h3 class="text-xl font-semibold mb-4 flex items-center"> |
|
|
<i class="fas fa-history mr-2 text-purple-600"></i> Backups Disponíveis |
|
|
</h3> |
|
|
<div class="overflow-x-auto"> |
|
|
<table class="min-w-full divide-y divide-gray-200"> |
|
|
<thead class="bg-gray-50"> |
|
|
<tr> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Data</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Tamanho</th> |
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Ações</th> |
|
|
</tr> |
|
|
</thead> |
|
|
<tbody class="bg-white divide-y divide-gray-200" id="backup-list"> |
|
|
|
|
|
</tbody> |
|
|
</table> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="customer-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> |
|
|
<div class="bg-white rounded-lg shadow-xl w-full max-w-2xl max-h-[90vh] overflow-y-auto"> |
|
|
<div class="p-6"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h3 class="text-xl font-semibold" id="modal-title">Adicionar Novo Cliente</h3> |
|
|
<button id="close-modal" class="text-gray-500 hover:text-gray-700"> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
</div> |
|
|
<form id="customer-form"> |
|
|
<input type="hidden" id="customer-id"> |
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4"> |
|
|
<div> |
|
|
<label for="customer-name" class="block text-gray-700 mb-2">Nome Completo*</label> |
|
|
<input type="text" id="customer-name" class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" required> |
|
|
</div> |
|
|
<div> |
|
|
<label for="customer-cpf" class="block text-gray-700 mb-2">CPF*</label> |
|
|
<input type="text" id="customer-cpf" class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" required> |
|
|
</div> |
|
|
</div> |
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4"> |
|
|
<div> |
|
|
<label for="customer-phone" class="block text-gray-700 mb-2">Telefone*</label> |
|
|
<input type="text" id="customer-phone" class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" required> |
|
|
</div> |
|
|
<div> |
|
|
<label for="customer-email" class="block text-gray-700 mb-2">Email</label> |
|
|
<input type="email" id="customer-email" class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
|
</div> |
|
|
</div> |
|
|
<div class="mb-4"> |
|
|
<label for="customer-address" class="block text-gray-700 mb-2">Endereço</label> |
|
|
<input type="text" id="customer-address" class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
|
</div> |
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6"> |
|
|
<div> |
|
|
<label for="customer-city" class="block text-gray-700 mb-2">Cidade</label> |
|
|
<input type="text" id="customer-city" class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
|
</div> |
|
|
<div> |
|
|
<label for="customer-state" class="block text-gray-700 mb-2">Estado</label> |
|
|
<input type="text" id="customer-state" class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
|
</div> |
|
|
<div> |
|
|
<label for="customer-zip" class="block text-gray-700 mb-2">CEP</label> |
|
|
<input type="text" id="customer-zip" class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> |
|
|
</div> |
|
|
</div> |
|
|
<div class="flex justify-end space-x-3"> |
|
|
<button type="button" id="cancel-customer" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-100">Cancelar</button> |
|
|
<button type="submit" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700">Salvar</button> |
|
|
</div> |
|
|
</form> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="toast" class="fixed bottom-4 right-4 bg-green-500 text-white px-6 py-3 rounded-md shadow-lg hidden"> |
|
|
<div class="flex items-center"> |
|
|
<i class="fas fa-check-circle mr-2"></i> |
|
|
<span id="toast-message">Operação realizada com sucesso!</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="confirm-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> |
|
|
<div class="bg-white rounded-lg shadow-xl w-full max-w-md"> |
|
|
<div class="p-6"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h3 class="text-xl font-semibold" id="confirm-title">Confirmar ação</h3> |
|
|
<button id="close-confirm" class="text-gray-500 hover:text-gray-700"> |
|
|
<i class="fas fa-times"></i> |
|
|
</button> |
|
|
</div> |
|
|
<p id="confirm-message">Tem certeza que deseja realizar esta ação?</p> |
|
|
<div class="flex justify-end space-x-3 mt-6"> |
|
|
<button type="button" id="cancel-confirm" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-100">Cancelar</button> |
|
|
<button type="button" id="confirm-action" class="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700">Confirmar</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
|
|
|
let db; |
|
|
const DB_NAME = "PharmaManagerDB"; |
|
|
const DB_VERSION = 1; |
|
|
|
|
|
|
|
|
const request = indexedDB.open(DB_NAME, DB_VERSION); |
|
|
|
|
|
request.onupgradeneeded = (event) => { |
|
|
db = event.target.result; |
|
|
|
|
|
|
|
|
if (!db.objectStoreNames.contains('customers')) { |
|
|
const customersStore = db.createObjectStore('customers', { keyPath: 'id', autoIncrement: true }); |
|
|
customersStore.createIndex('name', 'name', { unique: false }); |
|
|
customersStore.createIndex('cpf', 'cpf', { unique: true }); |
|
|
customersStore.createIndex('phone', 'phone', { unique: false }); |
|
|
} |
|
|
|
|
|
|
|
|
if (!db.objectStoreNames.contains('products')) { |
|
|
const productsStore = db.createObjectStore('products', { keyPath: 'id', autoIncrement: true }); |
|
|
productsStore.createIndex('name', 'name', { unique: false }); |
|
|
productsStore.createIndex('barcode', 'barcode', { unique: true }); |
|
|
} |
|
|
|
|
|
|
|
|
if (!db.objectStoreNames.contains('sales')) { |
|
|
const salesStore = db.createObjectStore('sales', { keyPath: 'id', autoIncrement: true }); |
|
|
salesStore.createIndex('customerId', 'customerId', { unique: false }); |
|
|
salesStore.createIndex('date', 'date', { unique: false }); |
|
|
} |
|
|
|
|
|
|
|
|
if (!db.objectStoreNames.contains('backups')) { |
|
|
db.createObjectStore('backups', { keyPath: 'id', autoIncrement: true }); |
|
|
} |
|
|
}; |
|
|
|
|
|
request.onsuccess = (event) => { |
|
|
db = event.target.result; |
|
|
console.log("Database opened successfully"); |
|
|
updateDashboardCounts(); |
|
|
loadCustomers(); |
|
|
loadBackups(); |
|
|
}; |
|
|
|
|
|
request.onerror = (event) => { |
|
|
console.error("Database error:", event.target.error); |
|
|
}; |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
|
|
|
const mobileMenuBtn = document.querySelector('.mobile-menu-btn'); |
|
|
const sidebar = document.querySelector('.sidebar'); |
|
|
const mainContent = document.querySelector('.main-content'); |
|
|
|
|
|
mobileMenuBtn.addEventListener('click', () => { |
|
|
sidebar.classList.toggle('hidden'); |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('customer-nav').addEventListener('click', () => { |
|
|
showSection('customer-content', 'Clientes'); |
|
|
loadCustomers(); |
|
|
}); |
|
|
|
|
|
document.getElementById('backup-nav').addEventListener('click', () => { |
|
|
showSection('backup-content', 'Backup/Restore'); |
|
|
loadBackups(); |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('add-customer-btn').addEventListener('click', () => { |
|
|
openCustomerModal(); |
|
|
}); |
|
|
|
|
|
document.getElementById('close-modal').addEventListener('click', () => { |
|
|
closeCustomerModal(); |
|
|
}); |
|
|
|
|
|
document.getElementById('cancel-customer').addEventListener('click', () => { |
|
|
closeCustomerModal(); |
|
|
}); |
|
|
|
|
|
document.getElementById('customer-form').addEventListener('submit', (e) => { |
|
|
e.preventDefault(); |
|
|
saveCustomer(); |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('create-backup-btn').addEventListener('click', () => { |
|
|
createBackup(); |
|
|
}); |
|
|
|
|
|
document.getElementById('restore-backup-btn').addEventListener('click', () => { |
|
|
restoreBackup(); |
|
|
}); |
|
|
|
|
|
|
|
|
showSection('dashboard-content', 'Dashboard'); |
|
|
}); |
|
|
|
|
|
function showSection(sectionId, title) { |
|
|
|
|
|
document.querySelectorAll('[id$="-content"]').forEach(section => { |
|
|
section.classList.add('hidden'); |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById(sectionId).classList.remove('hidden'); |
|
|
|
|
|
|
|
|
document.getElementById('page-title').textContent = title; |
|
|
} |
|
|
|
|
|
|
|
|
function loadCustomers(page = 1, search = '') { |
|
|
const transaction = db.transaction(['customers'], 'readonly'); |
|
|
const store = transaction.objectStore('customers'); |
|
|
const request = store.getAll(); |
|
|
|
|
|
request.onsuccess = () => { |
|
|
const customers = request.result; |
|
|
let filteredCustomers = customers; |
|
|
|
|
|
|
|
|
if (search) { |
|
|
const searchTerm = search.toLowerCase(); |
|
|
filteredCustomers = customers.filter(customer => |
|
|
customer.name.toLowerCase().includes(searchTerm) || |
|
|
customer.cpf.includes(searchTerm) || |
|
|
customer.phone.includes(searchTerm) || |
|
|
(customer.email && customer.email.toLowerCase().includes(searchTerm)) |
|
|
); |
|
|
} |
|
|
|
|
|
|
|
|
const itemsPerPage = 10; |
|
|
const startIndex = (page - 1) * itemsPerPage; |
|
|
const endIndex = startIndex + itemsPerPage; |
|
|
const paginatedCustomers = filteredCustomers.slice(startIndex, endIndex); |
|
|
const totalPages = Math.ceil(filteredCustomers.length / itemsPerPage); |
|
|
|
|
|
|
|
|
const tbody = document.getElementById('customer-table-body'); |
|
|
tbody.innerHTML = ''; |
|
|
|
|
|
if (paginatedCustomers.length === 0) { |
|
|
const row = document.createElement('tr'); |
|
|
row.innerHTML = `<td colspan="6" class="px-6 py-4 text-center text-gray-500">Nenhum cliente encontrado</td>`; |
|
|
tbody.appendChild(row); |
|
|
} else { |
|
|
paginatedCustomers.forEach(customer => { |
|
|
const row = document.createElement('tr'); |
|
|
row.innerHTML = ` |
|
|
<td class="px-6 py-4 whitespace-nowrap">${customer.id}</td> |
|
|
<td class="px-6 py-4 whitespace-nowrap">${customer.name}</td> |
|
|
<td class="px-6 py-4 whitespace-nowrap">${formatCPF(customer.cpf)}</td> |
|
|
<td class="px-6 py-4 whitespace-nowrap">${formatPhone(customer.phone)}</td> |
|
|
<td class="px-6 py-4 whitespace-nowrap">${customer.email || '-'}</td> |
|
|
<td class="px-6 py-4 whitespace-nowrap"> |
|
|
<button class="edit-customer text-blue-600 hover:text-blue-800 mr-3" data-id="${customer.id}"> |
|
|
<i class="fas fa-edit"></i> |
|
|
</button> |
|
|
<button class="delete-customer text-red-600 hover:text-red-800" data-id="${customer.id}"> |
|
|
<i class="fas fa-trash"></i> |
|
|
</button> |
|
|
</td> |
|
|
`; |
|
|
tbody.appendChild(row); |
|
|
}); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.edit-customer').forEach(btn => { |
|
|
btn.addEventListener('click', (e) => { |
|
|
const id = parseInt(e.currentTarget.getAttribute('data-id')); |
|
|
editCustomer(id); |
|
|
}); |
|
|
}); |
|
|
|
|
|
document.querySelectorAll('.delete-customer').forEach(btn => { |
|
|
btn.addEventListener('click', (e) => { |
|
|
const id = parseInt(e.currentTarget.getAttribute('data-id')); |
|
|
confirmDeleteCustomer(id); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
document.getElementById('customer-start').textContent = startIndex + 1; |
|
|
document.getElementById('customer-end').textContent = Math.min(endIndex, filteredCustomers.length); |
|
|
document.getElementById('customer-total').textContent = filteredCustomers.length; |
|
|
|
|
|
|
|
|
const prevBtn = document.getElementById('customer-prev'); |
|
|
const nextBtn = document.getElementById('customer-next'); |
|
|
|
|
|
prevBtn.disabled = page <= 1; |
|
|
nextBtn.disabled = page >= totalPages; |
|
|
|
|
|
prevBtn.onclick = () => loadCustomers(page - 1, search); |
|
|
nextBtn.onclick = () => loadCustomers(page + 1, search); |
|
|
}; |
|
|
|
|
|
request.onerror = () => { |
|
|
console.error("Error loading customers"); |
|
|
}; |
|
|
} |
|
|
|
|
|
function openCustomerModal(customer = null) { |
|
|
const modal = document.getElementById('customer-modal'); |
|
|
const form = document.getElementById('customer-form'); |
|
|
|
|
|
if (customer) { |
|
|
document.getElementById('modal-title').textContent = 'Editar Cliente'; |
|
|
document.getElementById('customer-id').value = customer.id; |
|
|
document.getElementById('customer-name').value = customer.name; |
|
|
document.getElementById('customer-cpf').value = customer.cpf; |
|
|
document.getElementById('customer-phone').value = customer.phone; |
|
|
document.getElementById('customer-email').value = customer.email || ''; |
|
|
document.getElementById('customer-address').value = customer.address || ''; |
|
|
document.getElementById('customer-city').value = customer.city || ''; |
|
|
document.getElementById('customer-state').value = customer.state || ''; |
|
|
document.getElementById('customer-zip').value = customer.zip || ''; |
|
|
} else { |
|
|
document.getElementById('modal-title').textContent = 'Adicionar Novo Cliente'; |
|
|
form.reset(); |
|
|
} |
|
|
|
|
|
modal.classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
function closeCustomerModal() { |
|
|
document.getElementById('customer-modal').classList.add('hidden'); |
|
|
} |
|
|
|
|
|
function saveCustomer() { |
|
|
const id = document.getElementById('customer-id').value; |
|
|
const customer = { |
|
|
name: document.getElementById('customer-name').value.trim(), |
|
|
cpf: document.getElementById('customer-cpf').value.replace(/\D/g, ''), |
|
|
phone: document.getElementById('customer-phone').value.replace(/\D/g, ''), |
|
|
email: document.getElementById('customer-email').value.trim() || null, |
|
|
address: document.getElementById('customer-address').value.trim() || null, |
|
|
city: document.getElementById('customer-city').value.trim() || null, |
|
|
state: document.getElementById('customer-state').value.trim() || null, |
|
|
zip: document.getElementById('customer-zip').value.replace(/\D/g, '') || null |
|
|
}; |
|
|
|
|
|
if (!customer.name || !customer.cpf || !customer.phone) { |
|
|
showToast('Por favor, preencha os campos obrigatórios', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const transaction = db.transaction(['customers'], 'readwrite'); |
|
|
const store = transaction.objectStore('customers'); |
|
|
|
|
|
if (id) { |
|
|
customer.id = parseInt(id); |
|
|
const request = store.put(customer); |
|
|
|
|
|
request.onsuccess = () => { |
|
|
showToast('Cliente atualizado com sucesso!'); |
|
|
closeCustomerModal(); |
|
|
loadCustomers(); |
|
|
updateDashboardCounts(); |
|
|
}; |
|
|
|
|
|
request.onerror = () => { |
|
|
showToast('Erro ao atualizar cliente', 'error'); |
|
|
}; |
|
|
} else { |
|
|
const request = store.add(customer); |
|
|
|
|
|
request.onsuccess = () => { |
|
|
showToast('Cliente cadastrado com sucesso!'); |
|
|
closeCustomerModal(); |
|
|
loadCustomers(); |
|
|
updateDashboardCounts(); |
|
|
}; |
|
|
|
|
|
request.onerror = (e) => { |
|
|
if (e.target.error.name === 'ConstraintError') { |
|
|
showToast('CPF já cadastrado', 'error'); |
|
|
} else { |
|
|
showToast('Erro ao cadastrar cliente', 'error'); |
|
|
} |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
function editCustomer(id) { |
|
|
const transaction = db.transaction(['customers'], 'readonly'); |
|
|
const store = transaction.objectStore('customers'); |
|
|
const request = store.get(id); |
|
|
|
|
|
request.onsuccess = () => { |
|
|
openCustomerModal(request.result); |
|
|
}; |
|
|
|
|
|
request.onerror = () => { |
|
|
showToast('Erro ao carregar cliente', 'error'); |
|
|
}; |
|
|
} |
|
|
|
|
|
function confirmDeleteCustomer(id) { |
|
|
const modal = document.getElementById('confirm-modal'); |
|
|
document.getElementById('confirm-title').textContent = 'Excluir Cliente'; |
|
|
document.getElementById('confirm-message').textContent = 'Tem certeza que deseja excluir este cliente? Esta ação não pode ser desfeita.'; |
|
|
|
|
|
document.getElementById('confirm-action').onclick = () => { |
|
|
deleteCustomer(id); |
|
|
modal.classList.add('hidden'); |
|
|
}; |
|
|
|
|
|
modal.classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
function deleteCustomer(id) { |
|
|
const transaction = db.transaction(['customers'], 'readwrite'); |
|
|
const store = transaction.objectStore('customers'); |
|
|
const request = store.delete(id); |
|
|
|
|
|
request.onsuccess = () => { |
|
|
showToast('Cliente excluído com sucesso!'); |
|
|
loadCustomers(); |
|
|
updateDashboardCounts(); |
|
|
}; |
|
|
|
|
|
request.onerror = () => { |
|
|
showToast('Erro ao excluir cliente', 'error'); |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
function createBackup() { |
|
|
|
|
|
const transaction = db.transaction(['customers', 'products', 'sales'], 'readonly'); |
|
|
|
|
|
const customersRequest = transaction.objectStore('customers').getAll(); |
|
|
const productsRequest = transaction.objectStore('products').getAll(); |
|
|
const salesRequest = transaction.objectStore('sales').getAll(); |
|
|
|
|
|
Promise.all([ |
|
|
new Promise(resolve => { customersRequest.onsuccess = () => resolve(customersRequest.result); }), |
|
|
new Promise(resolve => { productsRequest.onsuccess = () => resolve(productsRequest.result); }), |
|
|
new Promise(resolve => { salesRequest.onsuccess = () => resolve(salesRequest.result); }) |
|
|
]).then(([customers, products, sales]) => { |
|
|
const backupData = { |
|
|
timestamp: new Date().getTime(), |
|
|
date: new Date().toISOString(), |
|
|
customers, |
|
|
products, |
|
|
sales |
|
|
}; |
|
|
|
|
|
|
|
|
const backupTransaction = db.transaction(['backups'], 'readwrite'); |
|
|
const backupStore = backupTransaction.objectStore('backups'); |
|
|
backupStore.add(backupData); |
|
|
|
|
|
backupTransaction.oncomplete = () => { |
|
|
showToast('Backup criado com sucesso!'); |
|
|
loadBackups(); |
|
|
|
|
|
|
|
|
const dataStr = JSON.stringify(backupData); |
|
|
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr); |
|
|
|
|
|
const exportName = `pharmamanager_backup_${new Date().toISOString().slice(0, 10)}.json`; |
|
|
|
|
|
const linkElement = document.createElement('a'); |
|
|
linkElement.setAttribute('href', dataUri); |
|
|
linkElement.setAttribute('download', exportName); |
|
|
linkElement.click(); |
|
|
}; |
|
|
|
|
|
backupTransaction.onerror = () => { |
|
|
showToast('Erro ao salvar backup local', 'error'); |
|
|
}; |
|
|
}).catch(error => { |
|
|
console.error("Backup error:", error); |
|
|
showToast('Erro ao criar backup', 'error'); |
|
|
}); |
|
|
} |
|
|
|
|
|
function restoreBackup() { |
|
|
const fileInput = document.getElementById('restore-file'); |
|
|
const file = fileInput.files[0]; |
|
|
|
|
|
if (!file) { |
|
|
showToast('Selecione um arquivo de backup', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const reader = new FileReader(); |
|
|
reader.onload = (event) => { |
|
|
try { |
|
|
const backupData = JSON.parse(event.target.result); |
|
|
|
|
|
|
|
|
const modal = document.getElementById('confirm-modal'); |
|
|
document.getElementById('confirm-title').textContent = 'Restaurar Backup'; |
|
|
document.getElementById('confirm-message').textContent = `Tem certeza que deseja restaurar o backup de ${new Date(backupData.timestamp).toLocaleString()}? Todos os dados atuais serão substituídos.`; |
|
|
|
|
|
document.getElementById('confirm-action').onclick = () => { |
|
|
performRestore(backupData); |
|
|
modal.classList.add('hidden'); |
|
|
}; |
|
|
|
|
|
modal.classList.remove('hidden'); |
|
|
} catch (e) { |
|
|
console.error("Error parsing backup file:", e); |
|
|
showToast('Arquivo de backup inválido', 'error'); |
|
|
} |
|
|
}; |
|
|
reader.readAsText(file); |
|
|
} |
|
|
|
|
|
function performRestore(backupData) { |
|
|
|
|
|
const transaction = db.transaction(['customers', 'products', 'sales', 'backups'], 'readwrite'); |
|
|
|
|
|
|
|
|
transaction.objectStore('customers').clear(); |
|
|
transaction.objectStore('products').clear(); |
|
|
transaction.objectStore('sales').clear(); |
|
|
|
|
|
|
|
|
backupData.customers.forEach(customer => { |
|
|
transaction.objectStore('customers').add(customer); |
|
|
}); |
|
|
|
|
|
backupData.products.forEach(product => { |
|
|
transaction.objectStore('products').add(product); |
|
|
}); |
|
|
|
|
|
backupData.sales.forEach(sale => { |
|
|
transaction.objectStore('sales').add(sale); |
|
|
}); |
|
|
|
|
|
|
|
|
transaction.objectStore('backups').add(backupData); |
|
|
|
|
|
transaction.oncomplete = () => { |
|
|
showToast('Backup restaurado com sucesso!'); |
|
|
loadCustomers(); |
|
|
updateDashboardCounts(); |
|
|
loadBackups(); |
|
|
}; |
|
|
|
|
|
transaction.onerror = () => { |
|
|
showToast('Erro ao restaurar backup', 'error'); |
|
|
}; |
|
|
} |
|
|
|
|
|
function loadBackups() { |
|
|
const transaction = db.transaction(['backups'], 'readonly'); |
|
|
const store = transaction.objectStore('backups'); |
|
|
const request = store.getAll(); |
|
|
|
|
|
request.onsuccess = () => { |
|
|
const backups = request.result; |
|
|
const tbody = document.getElementById('backup-list'); |
|
|
tbody.innerHTML = ''; |
|
|
|
|
|
if (backups.length === 0) { |
|
|
const row = document.createElement('tr'); |
|
|
row.innerHTML = `<td colspan="3" class="px-6 py-4 text-center text-gray-500">Nenhum backup disponível</td>`; |
|
|
tbody.appendChild(row); |
|
|
} else { |
|
|
|
|
|
backups.sort((a, b) => b.timestamp - a.timestamp); |
|
|
|
|
|
backups.forEach(backup => { |
|
|
const date = new Date(backup.timestamp); |
|
|
const size = JSON.stringify(backup).length; |
|
|
const sizeKB = (size / 1024).toFixed(2); |
|
|
|
|
|
const row = document.createElement('tr'); |
|
|
row.innerHTML = ` |
|
|
<td class="px-6 py-4 whitespace-nowrap">${date.toLocaleString()}</td> |
|
|
<td class="px-6 py-4 whitespace-nowrap">${sizeKB} KB</td> |
|
|
<td class="px-6 py-4 whitespace-nowrap"> |
|
|
<button class="download-backup text-blue-600 hover:text-blue-800 mr-3" data-id="${backup.id}"> |
|
|
<i class="fas fa-download"></i> Download |
|
|
</button> |
|
|
<button class="restore-from-list text-green-600 hover:text-green-800" data-id="${backup.id}"> |
|
|
<i class="fas fa-redo"></i> Restaurar |
|
|
</button> |
|
|
</td> |
|
|
`; |
|
|
tbody.appendChild(row); |
|
|
}); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.download-backup').forEach(btn => { |
|
|
btn.addEventListener('click', (e) => { |
|
|
const id = parseInt(e.currentTarget.getAttribute('data-id')); |
|
|
downloadBackup(id); |
|
|
}); |
|
|
}); |
|
|
|
|
|
document.querySelectorAll('.restore-from-list').forEach(btn => { |
|
|
btn.addEventListener('click', (e) => { |
|
|
const id = parseInt(e.currentTarget.getAttribute('data-id')); |
|
|
confirmRestoreBackup(id); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
}; |
|
|
} |
|
|
|
|
|
function downloadBackup(id) { |
|
|
const transaction = db.transaction(['backups'], 'readonly'); |
|
|
const store = transaction.objectStore('backups'); |
|
|
const request = store.get(id); |
|
|
|
|
|
request.onsuccess = () => { |
|
|
const backupData = request.result; |
|
|
const dataStr = JSON.stringify(backupData); |
|
|
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr); |
|
|
|
|
|
const exportName = `pharmamanager_backup_${new Date(backupData.timestamp).toISOString().slice(0, 10)}.json`; |
|
|
|
|
|
const linkElement = document.createElement('a'); |
|
|
linkElement.setAttribute('href', dataUri); |
|
|
linkElement.setAttribute('download', exportName); |
|
|
linkElement.click(); |
|
|
|
|
|
showToast('Download do backup iniciado'); |
|
|
}; |
|
|
} |
|
|
|
|
|
function confirmRestoreBackup(id) { |
|
|
const modal = document.getElementById('confirm-modal'); |
|
|
document.getElementById('confirm-title').textContent = 'Restaurar Backup'; |
|
|
document.getElementById('confirm-message').textContent = 'Tem certeza que deseja restaurar este backup? Todos os dados atuais serão substituídos.'; |
|
|
|
|
|
document.getElementById('confirm-action').onclick = () => { |
|
|
restoreFromBackup(id); |
|
|
modal.classList.add('hidden'); |
|
|
}; |
|
|
|
|
|
modal.classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
function restoreFromBackup(id) { |
|
|
const transaction = db.transaction(['backups'], 'readonly'); |
|
|
const store = transaction.objectStore('backups'); |
|
|
const request = store.get(id); |
|
|
|
|
|
request.onsuccess = () => { |
|
|
performRestore(request.result); |
|
|
}; |
|
|
|
|
|
request.onerror = () => { |
|
|
showToast('Erro ao carregar backup', 'error'); |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
function updateDashboardCounts() { |
|
|
|
|
|
const customerTransaction = db.transaction(['customers'], 'readonly'); |
|
|
const customerStore = customerTransaction.objectStore('customers'); |
|
|
const customerCountRequest = customerStore.count(); |
|
|
|
|
|
customerCountRequest.onsuccess = () => { |
|
|
document.getElementById('customer-count').textContent = customerCountRequest.result; |
|
|
}; |
|
|
|
|
|
|
|
|
const productTransaction = db.transaction(['products'], 'readonly'); |
|
|
const productStore = productTransaction.objectStore('products'); |
|
|
const productCountRequest = productStore.count(); |
|
|
|
|
|
productCountRequest.onsuccess = () => { |
|
|
document.getElementById('product-count').textContent = productCountRequest.result; |
|
|
}; |
|
|
|
|
|
|
|
|
const salesTransaction = db.transaction(['sales'], 'readonly'); |
|
|
const salesStore = salesTransaction.objectStore('sales'); |
|
|
const today = new Date().toISOString().slice(0, 10); |
|
|
const salesTodayRequest = salesStore.index('date').getAll(today); |
|
|
|
|
|
salesTodayRequest.onsuccess = () => { |
|
|
document.getElementById('sales-today').textContent = salesTodayRequest.result.length; |
|
|
}; |
|
|
|
|
|
|
|
|
document.getElementById('monthly-revenue').textContent = 'R$ 0,00'; |
|
|
} |
|
|
|
|
|
|
|
|
function showToast(message, type = 'success') { |
|
|
const toast = document.getElementById('toast'); |
|
|
const toastMessage = document.getElementById('toast-message'); |
|
|
|
|
|
toastMessage.textContent = message; |
|
|
|
|
|
|
|
|
if (type === 'error') { |
|
|
toast.classList.remove('bg-green-500'); |
|
|
toast.classList.add('bg-red-500'); |
|
|
} else { |
|
|
toast.classList.remove('bg-red-500'); |
|
|
toast.classList.add('bg-green-500'); |
|
|
} |
|
|
|
|
|
toast.classList.remove('hidden'); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
toast.classList.add('hidden'); |
|
|
}, 3000); |
|
|
} |
|
|
|
|
|
function formatCPF(cpf) { |
|
|
return cpf.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3-$4'); |
|
|
} |
|
|
|
|
|
function formatPhone(phone) { |
|
|
if (phone.length === 11) { |
|
|
return phone.replace(/(\d{2})(\d{5})(\d{4})/, '($1) $2-$3'); |
|
|
} else if (phone.length === 10) { |
|
|
return phone.replace(/(\d{2})(\d{4})(\d{4})/, '($1) $2-$3'); |
|
|
} |
|
|
return phone; |
|
|
} |
|
|
|
|
|
|
|
|
document.getElementById('customer-search').addEventListener('input', (e) => { |
|
|
loadCustomers(1, e.target.value); |
|
|
}); |
|
|
|
|
|
|
|
|
window.addEventListener('click', (e) => { |
|
|
const customerModal = document.getElementById('customer-modal'); |
|
|
if (e.target === customerModal) { |
|
|
closeCustomerModal(); |
|
|
} |
|
|
|
|
|
const confirmModal = document.getElementById('confirm-modal'); |
|
|
if (e.target === confirmModal) { |
|
|
confirmModal.classList.add('hidden'); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('close-confirm').addEventListener('click', () => { |
|
|
document.getElementById('confirm-modal').classList.add('hidden'); |
|
|
}); |
|
|
|
|
|
document.getElementById('cancel-confirm').addEventListener('click', () => { |
|
|
document.getElementById('confirm-modal').classList.add('hidden'); |
|
|
}); |
|
|
</script> |
|
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=jptec2024/teste" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
|
</html> |