From 404bcf86370b3aa54e5c09e50a7be47241fe63ee Mon Sep 17 00:00:00 2001 From: Ricardo <230414@epvc.pt> Date: Wed, 18 Mar 2026 10:37:10 +0000 Subject: [PATCH] ajuste tela de login --- .vscode/settings.json | 3 + index.html | 1403 +++++++++++++++++++++++++++++++++++++++++ manifest.json | 20 + script.js | 492 +++++++++++++++ style.css | 81 +++ sw.js | 28 + 6 files changed, 2027 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 index.html create mode 100644 manifest.json create mode 100644 script.js create mode 100644 style.css create mode 100644 sw.js diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..6f3a291 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "liveServer.settings.port": 5501 +} \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..2c37329 --- /dev/null +++ b/index.html @@ -0,0 +1,1403 @@ + + + + + + + CondoMaster Pro + + + + + + + + + + + + +
+ + + + + + \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..64252d0 --- /dev/null +++ b/manifest.json @@ -0,0 +1,20 @@ +{ + "name": "CondoSync Gestão", + "short_name": "CondoSync", + "start_url": "./index.html", + "display": "standalone", + "background_color": "#2c3e50", + "theme_color": "#2c3e50", + "icons": [ + { + "src": "https://cdn-icons-png.flaticon.com/512/609/609803.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "https://cdn-icons-png.flaticon.com/512/609/609803.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/script.js b/script.js new file mode 100644 index 0000000..f37d721 --- /dev/null +++ b/script.js @@ -0,0 +1,492 @@ + + +const SUPABASE_URL = 'YOUR_SUPABASE_URL'; +const SUPABASE_KEY = 'YOUR_SUPABASE_KEY'; + + +const IS_MOCK = SUPABASE_URL.includes('YOUR_SUPABASE'); + +let supabase; + +if (!IS_MOCK) { + supabase = supabase.createClient(SUPABASE_URL, SUPABASE_KEY); +} else { + console.warn("⚠️ MOCK MODE ATIVADO: Usando LocalStorage. Configure o Supabase para persistência real."); + showToast("Modo Demo Ativado (LocalStorage)", "warning"); +} + +document.addEventListener('DOMContentLoaded', () => { + checkLogin(); + setupEventListeners(); +}); + +function setupEventListeners() { + + document.getElementById('login-form').addEventListener('submit', handleLogin); + + + document.getElementById('form-morador').addEventListener('submit', saveMorador); + document.getElementById('form-transacao').addEventListener('submit', saveTransacao); + document.getElementById('form-ocorrencia').addEventListener('submit', saveOcorrencia); + document.getElementById('form-aviso').addEventListener('submit', saveAviso); + + + if (sessionStorage.getItem('condoProUser')) { + renderDashboard(); + } +} + + +function checkLogin() { + const user = sessionStorage.getItem('condoProUser'); + if (user) { + document.getElementById('login-section').classList.add('d-none'); + document.getElementById('app-layout').classList.remove('d-none'); + document.getElementById('app-layout').classList.add('d-flex'); + } else { + document.getElementById('login-section').classList.remove('d-none'); + document.getElementById('app-layout').classList.add('d-none'); + document.getElementById('app-layout').classList.remove('d-flex'); + } +} + +function handleLogin(e) { + e.preventDefault(); + const pass = document.getElementById('login-password').value; + if (pass === 'admin123') { + sessionStorage.setItem('condoProUser', 'admin'); + checkLogin(); + renderDashboard(); + showToast("Bem-vindo ao CondoPro!", "success"); + } else { + document.getElementById('login-error').classList.remove('d-none'); + showToast("Senha incorreta!", "danger"); + } +} + +function logout() { + sessionStorage.removeItem('condoProUser'); + checkLogin(); + showToast("Logout realizado.", "info"); +} + + +function showToast(message, type = 'primary') { + const container = document.getElementById('toast-container'); + const toastEl = document.createElement('div'); + toastEl.className = `toast align-items-center text-white bg-${type} border-0`; + toastEl.setAttribute('role', 'alert'); + toastEl.setAttribute('aria-live', 'assertive'); + toastEl.setAttribute('aria-atomic', 'true'); + + toastEl.innerHTML = ` +
+
+ ${message} +
+ +
+ `; + + container.appendChild(toastEl); + const toast = new bootstrap.Toast(toastEl, { delay: 3000 }); + toast.show(); + + toastEl.addEventListener('hidden.bs.toast', () => { + toastEl.remove(); + }); +} + + +async function dbSelect(table, orderBy = null) { + if (IS_MOCK) { + const data = JSON.parse(localStorage.getItem(`condopro_${table}`)) || []; + if (orderBy) { + data.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); + } + return { data, error: null }; + } + let query = supabase.from(table).select('*'); + if (orderBy) query = query.order(orderBy.col, { ascending: orderBy.asc }); + return await query; +} + +async function dbInsert(table, row) { + row.created_at = new Date().toISOString(); + if (!row.id) row.id = Date.now(); + + if (IS_MOCK) { + const data = JSON.parse(localStorage.getItem(`condopro_${table}`)) || []; + data.push(row); + localStorage.setItem(`condopro_${table}`, JSON.stringify(data)); + return { data: [row], error: null }; + } + return await supabase.from(table).insert([row]); +} + +async function dbDelete(table, id) { + if (IS_MOCK) { + let data = JSON.parse(localStorage.getItem(`condopro_${table}`)) || []; + data = data.filter(item => item.id != id); // Loose comparison for ID + localStorage.setItem(`condopro_${table}`, JSON.stringify(data)); + return { error: null }; + } + return await supabase.from(table).delete().eq('id', id); +} + +function navigateTo(viewId) { + document.querySelectorAll('.nav-link').forEach(link => link.classList.remove('active')); + event.currentTarget.classList.add('active'); + + document.querySelectorAll('.content-view').forEach(view => view.classList.add('d-none')); + document.getElementById(`view-${viewId}`).classList.remove('d-none'); + + if (viewId === 'dashboard') renderDashboard(); + if (viewId === 'moradores') renderMoradores(); + if (viewId === 'financeiro') renderFinanceiro(); + if (viewId === 'ocorrencias') renderOcorrencias(); + if (viewId === 'avisos') renderAvisos(); + + document.getElementById('sidebar').classList.remove('show'); +} + +function toggleSidebar() { + document.getElementById('sidebar').classList.toggle('show'); +} + +let financeChartInstance = null; + +async function renderDashboard() { + const { data: financeiro } = await dbSelect('financeiro'); + const { data: moradores } = await dbSelect('moradores'); + const { data: ocorrencias } = await dbSelect('ocorrencias'); + + let saldoTotal = 0; + let receitasMes = 0; + let despesasMes = 0; + + if (financeiro) { + financeiro.forEach(t => { + const val = parseFloat(t.valor); + if (t.tipo === 'receita') { + saldoTotal += val; + receitasMes += val; + } else { + saldoTotal -= val; + despesasMes += val; + } + }); + } + + const ocorrenciasPendentes = ocorrencias ? ocorrencias.filter(o => o.status === 'pendente').length : 0; + + document.getElementById('dash-saldo').innerText = `R$ ${saldoTotal.toFixed(2)}`; + document.getElementById('dash-receitas').innerText = `R$ ${receitasMes.toFixed(2)}`; + document.getElementById('dash-despesas').innerText = `R$ ${despesasMes.toFixed(2)}`; + document.getElementById('dash-moradores-count').innerText = moradores ? moradores.length : 0; + document.getElementById('dash-ocorrencias-count').innerText = ocorrenciasPendentes; + + renderChart(financeiro || []); +} + +function renderChart(transactions) { + const ctx = document.getElementById('financeChart').getContext('2d'); + if (financeChartInstance) financeChartInstance.destroy(); + const labels = ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun']; + const dataReceitas = [0, 0, 0, 0, 0, 0]; + const dataDespesas = [0, 0, 0, 0, 0, 0]; + + if (transactions.length > 0) { + dataReceitas[5] = transactions.filter(t => t.tipo === 'receita').reduce((a, b) => a + parseFloat(b.valor), 0); + dataDespesas[5] = transactions.filter(t => t.tipo === 'despesa').reduce((a, b) => a + parseFloat(b.valor), 0); + } else { + dataReceitas.splice(0, 6, 1200, 1500, 1100, 1800, 2000, 2200); + dataDespesas.splice(0, 6, 800, 900, 700, 1000, 950, 1100); + } + + financeChartInstance = new Chart(ctx, { + type: 'bar', + data: { + labels: labels, + datasets: [ + { label: 'Receitas', data: dataReceitas, backgroundColor: '#2ecc71', borderRadius: 4 }, + { label: 'Despesas', data: dataDespesas, backgroundColor: '#e74c3c', borderRadius: 4 } + ] + }, + options: { + responsive: true, + plugins: { + legend: { position: 'bottom' } + }, + scales: { + y: { beginAtZero: true, grid: { color: 'rgba(0,0,0,0.05)' } }, + x: { grid: { display: false } } + } + } + }); +} + +async function renderMoradores() { + const tbody = document.getElementById('moradores-list'); + tbody.innerHTML = 'Carregando...'; + + const { data, error } = await dbSelect('moradores'); + + if (error) { + tbody.innerHTML = 'Erro ao carregar dados.'; + showToast("Erro ao carregar moradores", "danger"); + return; + } + + tbody.innerHTML = ''; + if (data.length === 0) { + tbody.innerHTML = 'Nenhum morador cadastrado.'; + return; + } + + data.forEach(m => { + const tr = document.createElement('tr'); + tr.innerHTML = ` +
${m.nome}
${m.email || ''} + ${m.bloco} + ${m.apartamento} + ${m.telefone || '-'} + + + + `; + tbody.appendChild(tr); + }); +} + +function prepareCreateMorador() { + document.getElementById('form-morador').reset(); +} + +async function saveMorador(e) { + e.preventDefault(); + const nome = document.getElementById('morador-nome').value; + const bloco = document.getElementById('morador-bloco').value; + const apartamento = document.getElementById('morador-apto').value; + const telefone = document.getElementById('morador-telefone').value; + const email = document.getElementById('morador-email').value; + + const { error } = await dbInsert('moradores', { nome, bloco, apartamento, telefone, email }); + + if (error) showToast("Erro ao salvar: " + error.message, "danger"); + else { + bootstrap.Modal.getInstance(document.getElementById('modalMorador')).hide(); + renderMoradores(); + showToast("Morador salvo com sucesso!", "success"); + } +} + +async function renderFinanceiro() { + const tbody = document.getElementById('financeiro-list'); + tbody.innerHTML = 'Carregando...'; + + const { data, error } = await dbSelect('financeiro', { col: 'created_at', asc: false }); + + if (error) { + tbody.innerHTML = 'Erro ao carregar.'; + return; + } + + tbody.innerHTML = ''; + if (data.length === 0) { + tbody.innerHTML = 'Nenhuma transação encontrada.'; + return; + } + + data.forEach(t => { + const tr = document.createElement('tr'); + const badgeClass = t.tipo === 'receita' ? 'bg-success' : 'bg-danger'; + const icon = t.tipo === 'receita' ? 'fa-arrow-up' : 'fa-arrow-down'; + + tr.innerHTML = ` + ${new Date(t.data).toLocaleDateString()} + ${t.descricao} + ${t.tipo.toUpperCase()} + R$ ${parseFloat(t.valor).toFixed(2)} + + + + `; + tbody.appendChild(tr); + }); +} + +function prepareCreateTransacao() { + document.getElementById('form-transacao').reset(); +} + +async function saveTransacao(e) { + e.preventDefault(); + const descricao = document.getElementById('transacao-descricao').value; + const tipo = document.getElementById('transacao-tipo').value; + const valor = document.getElementById('transacao-valor').value; + const data = document.getElementById('transacao-data').value; + + const { error } = await dbInsert('financeiro', { descricao, tipo, valor, data }); + + if (error) showToast("Erro: " + error.message, "danger"); + else { + bootstrap.Modal.getInstance(document.getElementById('modalTransacao')).hide(); + renderFinanceiro(); + showToast("Transação registrada!", "success"); + } +} + +async function renderOcorrencias() { + const container = document.getElementById('ocorrencias-list'); + const { data, error } = await dbSelect('ocorrencias', { col: 'created_at', asc: false }); + + if (error) { + container.innerHTML = '

Erro ao carregar

'; + return; + } + + container.innerHTML = ''; + if (data.length === 0) { + container.innerHTML = '

Nenhuma ocorrência pendente.

'; + return; + } + + data.forEach(o => { + const card = document.createElement('div'); + card.className = 'col-md-4 mb-4 fade-in'; + card.innerHTML = ` +
+ ${o.imagem_url ? `Ocorrência` : ''} +
+
+
${o.titulo}
+ Pendente +
+

${o.descricao}

+
+ ${new Date(o.created_at).toLocaleDateString()} + +
+
+
+ `; + container.appendChild(card); + }); +} + +function prepareCreateOcorrencia() { + document.getElementById('form-ocorrencia').reset(); +} + +async function saveOcorrencia(e) { + e.preventDefault(); + const titulo = document.getElementById('ocorrencia-titulo').value; + const descricao = document.getElementById('ocorrencia-descricao').value; + const fileInput = document.getElementById('ocorrencia-file'); + let imagem_url = null; + + if (IS_MOCK && fileInput.files.length > 0) { + showToast("Upload simulado (Mock Mode)", "info"); + imagem_url = "https://placehold.co/600x400?text=Imagem+Ocorrencia"; + } else if (fileInput.files.length > 0) { + const file = fileInput.files[0]; + const fileName = `${Date.now()}_${file.name}`; + const { data, error } = await supabase.storage.from('condopro-bucket').upload(fileName, file); + + if (error) { + showToast('Erro upload imagem: ' + error.message, "danger"); + return; + } + const { data: publicData } = supabase.storage.from('condopro-bucket').getPublicUrl(fileName); + imagem_url = publicData.publicUrl; + } + + const { error } = await dbInsert('ocorrencias', { titulo, descricao, imagem_url, status: 'pendente' }); + + if (error) showToast("Erro: " + error.message, "danger"); + else { + bootstrap.Modal.getInstance(document.getElementById('modalOcorrencia')).hide(); + renderOcorrencias(); + showToast("Ocorrência reportada!", "success"); + } +} + +async function resolveOcorrencia(id) { + await dbDelete('ocorrencias', id); + renderOcorrencias(); + showToast("Ocorrência marcada como resolvida!", "success"); +} + +async function renderAvisos() { + const list = document.getElementById('avisos-list'); + const { data, error } = await dbSelect('avisos', { col: 'created_at', asc: false }); + + if (error) { + list.innerHTML = 'Erro ao carregar'; + return; + } + + list.innerHTML = ''; + if (data.length === 0) { + list.innerHTML = '

Nenhum aviso no mural.

'; + return; + } + + data.forEach(a => { + const item = document.createElement('a'); + item.className = 'list-group-item list-group-item-action flex-column align-items-start border-start border-4 border-info shadow-sm mb-2'; + item.innerHTML = ` +
+
${a.titulo}
+ ${new Date(a.created_at).toLocaleDateString()} +
+

${a.mensagem}

+ `; + list.appendChild(item); + }); +} + +function prepareCreateAviso() { + document.getElementById('form-aviso').reset(); +} + +async function saveAviso(e) { + e.preventDefault(); + const titulo = document.getElementById('aviso-titulo').value; + const mensagem = document.getElementById('aviso-mensagem').value; + + const { error } = await dbInsert('avisos', { titulo, mensagem }); + + if (error) showToast("Erro: " + error.message, "danger"); + else { + bootstrap.Modal.getInstance(document.getElementById('modalAviso')).hide(); + renderAvisos(); + showToast("Aviso publicado!", "info"); + } +} + + +async function deleteItem(table, id) { + if (confirm('Tem certeza que deseja excluir?')) { + const { error } = await dbDelete(table, id); + if (error) showToast("Erro ao excluir", "danger"); + else { + showToast("Item excluído.", "success"); + if (table === 'moradores') renderMoradores(); + if (table === 'financeiro') renderFinanceiro(); + } + } +} + +function exportPDF(tableId, filename) { + const { jsPDF } = window.jspdf; + const doc = new jsPDF(); + + doc.text(filename.replace('_', ' '), 14, 15); + doc.autoTable({ html: '#' + tableId, startY: 20 }); + doc.save(filename + '.pdf'); + showToast("PDF gerado com sucesso!", "success"); +} diff --git a/style.css b/style.css new file mode 100644 index 0000000..b123a03 --- /dev/null +++ b/style.css @@ -0,0 +1,81 @@ +:root { + --sidebar-width: 250px; + --primary-color: #3498db; + --secondary-color: #2c3e50; + --background-color: #f8f9fa; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background-color: var(--background-color); +} + +.sidebar { + width: var(--sidebar-width); + background-color: var(--secondary-color); + min-height: 100vh; + transition: all 0.3s; +} + +.nav-link { + color: rgba(255, 255, 255, 0.8) !important; + margin-bottom: 5px; + transition: all 0.2s; +} + +.nav-link:hover, .nav-link.active { + background-color: var(--primary-color) !important; + color: white !important; + transform: translateX(5px); +} + +.nav-link i { + width: 25px; + text-align: center; +} + +.card { + border: none; + transition: transform 0.2s ease-in-out, box-shadow 0.2s; + border-radius: 10px; +} + +.card:hover { + transform: translateY(-3px); + box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important; +} + +@media (max-width: 768px) { + .sidebar { + width: 100%; + height: auto; + min-height: 0; + position: absolute; + z-index: 1000; + transform: translateX(-100%); + } + + .sidebar.show { + transform: translateX(0); + } + + #app-layout { + flex-direction: column; + } +} + +.cursor-pointer { + cursor: pointer; +} + +.transition-width { + transition: width 0.3s ease; +} + +.ocorrencia-img { + height: 200px; + object-fit: cover; + width: 100%; + border-top-left-radius: 10px; + border-top-right-radius: 10px; +} diff --git a/sw.js b/sw.js new file mode 100644 index 0000000..e06cdd4 --- /dev/null +++ b/sw.js @@ -0,0 +1,28 @@ +const CACHE_NAME = 'condopro-v1'; +const ASSETS_TO_CACHE = [ + './', + './index.html', + './style.css', + './script.js', + 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css', + 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css', + 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js', + 'https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2', + 'https://cdn.jsdelivr.net/npm/chart.js', + 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js', + 'https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.5.23/jspdf.plugin.autotable.min.js' +]; + +self.addEventListener('install', (event) => { + event.waitUntil( + caches.open(CACHE_NAME) + .then((cache) => cache.addAll(ASSETS_TO_CACHE)) + ); +}); + +self.addEventListener('fetch', (event) => { + event.respondWith( + caches.match(event.request) + .then((response) => response || fetch(event.request)) + ); +});