notificações
This commit is contained in:
84
index.html
84
index.html
@@ -100,7 +100,7 @@
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { app } from './firebase.js';
|
import { app } from './firebase.js';
|
||||||
import { getAuth, signInWithEmailAndPassword, createUserWithEmailAndPassword } from 'https://www.gstatic.com/firebasejs/12.1.0/firebase-auth.js';
|
import { getAuth, signInWithEmailAndPassword, createUserWithEmailAndPassword } from 'https://www.gstatic.com/firebasejs/12.1.0/firebase-auth.js';
|
||||||
import { getDatabase, ref, push, set, onValue, remove } from 'https://www.gstatic.com/firebasejs/12.1.0/firebase-database.js';
|
import { getDatabase, ref, push, set, onValue, remove, update } from 'https://www.gstatic.com/firebasejs/12.1.0/firebase-database.js';
|
||||||
|
|
||||||
const auth = getAuth(app);
|
const auth = getAuth(app);
|
||||||
const db = getDatabase(app);
|
const db = getDatabase(app);
|
||||||
@@ -141,16 +141,8 @@
|
|||||||
|
|
||||||
// --- VALIDAÇÕES OFICIAIS ---
|
// --- VALIDAÇÕES OFICIAIS ---
|
||||||
function validarNIF(nif) {
|
function validarNIF(nif) {
|
||||||
nif = nif.replace(/\s+/g, '');
|
nif = String(nif).replace(/\s+/g, '');
|
||||||
if (!/^\d{9}$/.test(nif)) return false;
|
return /^\d{9}$/.test(nif);
|
||||||
if (nif.charAt(0) === '0') return false;
|
|
||||||
let soma = 0;
|
|
||||||
for (let i = 0; i < 8; i++) {
|
|
||||||
soma += parseInt(nif.charAt(i), 10) * (9 - i);
|
|
||||||
}
|
|
||||||
const resto = soma % 11;
|
|
||||||
const digitoControlo = (resto === 0 || resto === 1) ? 0 : (11 - resto);
|
|
||||||
return digitoControlo === parseInt(nif.charAt(8), 10);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function validarDocumento(doc) {
|
function validarDocumento(doc) {
|
||||||
@@ -554,6 +546,14 @@
|
|||||||
unit: data.unit,
|
unit: data.unit,
|
||||||
pending: 0
|
pending: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await push(ref(db, `notificacoes/admin`), {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
message: `Novo pedido de registo: ${data.name} (${data.unit}). A aguardar aprovação.`,
|
||||||
|
time: 'Agora',
|
||||||
|
type: 'info',
|
||||||
|
read: false
|
||||||
|
});
|
||||||
|
|
||||||
sessionStorage.setItem('condo_auth', 'true');
|
sessionStorage.setItem('condo_auth', 'true');
|
||||||
sessionStorage.setItem('condo_role', 'morador');
|
sessionStorage.setItem('condo_role', 'morador');
|
||||||
@@ -599,6 +599,14 @@
|
|||||||
pending: 0,
|
pending: 0,
|
||||||
contact: data.password // Guardado no campo contact apenas para o fallback mock local de login funcionar
|
contact: data.password // Guardado no campo contact apenas para o fallback mock local de login funcionar
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await push(ref(db, `notificacoes/admin`), {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
message: `Novo pedido de registo: ${data.name} (${data.unit}). A aguardar aprovação.`,
|
||||||
|
time: 'Agora',
|
||||||
|
type: 'info',
|
||||||
|
read: false
|
||||||
|
});
|
||||||
} catch(dbErr) {
|
} catch(dbErr) {
|
||||||
console.error("Base de dados inacessível no fallback.", dbErr);
|
console.error("Base de dados inacessível no fallback.", dbErr);
|
||||||
}
|
}
|
||||||
@@ -854,7 +862,19 @@
|
|||||||
|
|
||||||
const sendSystemNotification = async (message, type = 'info', targetUserId = 'admin') => {
|
const sendSystemNotification = async (message, type = 'info', targetUserId = 'admin') => {
|
||||||
const newNotif = { timestamp: Date.now(), message, time: 'Agora', type, read: false };
|
const newNotif = { timestamp: Date.now(), message, time: 'Agora', type, read: false };
|
||||||
await push(ref(db, `notificacoes/${targetUserId}`), newNotif);
|
if (targetUserId === 'todos') {
|
||||||
|
const promises = residents.map(r => push(ref(db, `notificacoes/${r.id}`), newNotif));
|
||||||
|
promises.push(push(ref(db, `notificacoes/admin`), newNotif));
|
||||||
|
await Promise.all(promises);
|
||||||
|
} else {
|
||||||
|
await push(ref(db, `notificacoes/${targetUserId}`), newNotif);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMarkAsRead = async (notifId) => {
|
||||||
|
const targetFolder = userRole === 'admin' ? 'admin' : currentUserId;
|
||||||
|
const notifRef = ref(db, `notificacoes/${targetFolder}/${notifId}`);
|
||||||
|
await update(notifRef, { read: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
const showNotification = (message, type = 'success') => {
|
const showNotification = (message, type = 'success') => {
|
||||||
@@ -980,6 +1000,13 @@
|
|||||||
const amount = Number(formData.amount);
|
const amount = Number(formData.amount);
|
||||||
const newFinanceRef = push(ref(db, 'financas'));
|
const newFinanceRef = push(ref(db, 'financas'));
|
||||||
await set(newFinanceRef, { ...formData, amount });
|
await set(newFinanceRef, { ...formData, amount });
|
||||||
|
|
||||||
|
if (formData.type === 'expense') {
|
||||||
|
sendSystemNotification(`Nova despesa registada: ${formData.category} - ${amount.toFixed(2)}€`, 'warning', 'admin');
|
||||||
|
} else {
|
||||||
|
sendSystemNotification(`Nova receita registada: ${formData.category} - ${amount.toFixed(2)}€`, 'success', 'admin');
|
||||||
|
}
|
||||||
|
|
||||||
showNotification(`Movimento de ${amount}€ registado`);
|
showNotification(`Movimento de ${amount}€ registado`);
|
||||||
handleCloseModal();
|
handleCloseModal();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -997,6 +1024,12 @@
|
|||||||
try {
|
try {
|
||||||
const newIssueRef = push(ref(db, 'manutencao'));
|
const newIssueRef = push(ref(db, 'manutencao'));
|
||||||
await set(newIssueRef, { ...formData });
|
await set(newIssueRef, { ...formData });
|
||||||
|
|
||||||
|
sendSystemNotification(`Nova ocorrência reportada: ${formData.title} (${formData.location})`, 'warning', 'admin');
|
||||||
|
if (userRole !== 'admin') {
|
||||||
|
sendSystemNotification(`A sua ocorrência "${formData.title}" foi reportada com sucesso.`, 'info', currentUserId);
|
||||||
|
}
|
||||||
|
|
||||||
showNotification('Nova ocorrência reportada', 'warning');
|
showNotification('Nova ocorrência reportada', 'warning');
|
||||||
handleCloseModal();
|
handleCloseModal();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1031,6 +1064,9 @@
|
|||||||
const newPending = (Number(morador.pending) || 0) + valor;
|
const newPending = (Number(morador.pending) || 0) + valor;
|
||||||
await set(ref(db, `condominos/${morador.id}/pending`), newPending);
|
await set(ref(db, `condominos/${morador.id}/pending`), newPending);
|
||||||
|
|
||||||
|
sendSystemNotification(`Foi emitida uma nova fatura no valor de ${valor.toFixed(2)}€ (Categoria: ${formData.categoria})`, 'warning', morador.id);
|
||||||
|
sendSystemNotification(`Fatura de ${valor.toFixed(2)}€ emitida para ${morador.name} (${morador.unit})`, 'info', 'admin');
|
||||||
|
|
||||||
showNotification(`Fatura de ${valor.toFixed(2)}€ emitida para ${morador.name}`);
|
showNotification(`Fatura de ${valor.toFixed(2)}€ emitida para ${morador.name}`);
|
||||||
handleCloseModal();
|
handleCloseModal();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1042,6 +1078,7 @@
|
|||||||
const handlePayFatura = async (fatura) => {
|
const handlePayFatura = async (fatura) => {
|
||||||
try {
|
try {
|
||||||
await set(ref(db, `faturas/${fatura.id}/status`), 'Em Validação');
|
await set(ref(db, `faturas/${fatura.id}/status`), 'Em Validação');
|
||||||
|
sendSystemNotification(`O pagamento da sua fatura de ${fatura.categoria} foi submetido. Aguarda validação.`, 'info', fatura.moradorId);
|
||||||
sendSystemNotification(`Comprovativo recebido da fração ${fatura.fracao}.`, 'info', 'admin');
|
sendSystemNotification(`Comprovativo recebido da fração ${fatura.fracao}.`, 'info', 'admin');
|
||||||
showNotification("Comprovativo enviado! A aguardar validação do administrador.", "success");
|
showNotification("Comprovativo enviado! A aguardar validação do administrador.", "success");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1061,6 +1098,7 @@
|
|||||||
await set(ref(db, `condominos/${morador.id}/pending`), newPending);
|
await set(ref(db, `condominos/${morador.id}/pending`), newPending);
|
||||||
}
|
}
|
||||||
sendSystemNotification(`O seu pagamento da fatura de ${fatura.categoria} foi aprovado!`, 'success', fatura.moradorId);
|
sendSystemNotification(`O seu pagamento da fatura de ${fatura.categoria} foi aprovado!`, 'success', fatura.moradorId);
|
||||||
|
sendSystemNotification(`Pagamento aprovado para a fatura de ${fatura.categoria} da fração ${morador?.unit || fatura.fracao}.`, 'success', 'admin');
|
||||||
showNotification("Pagamento aprovado com sucesso!", "success");
|
showNotification("Pagamento aprovado com sucesso!", "success");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Erro ao aprovar fatura:", error);
|
console.error("Erro ao aprovar fatura:", error);
|
||||||
@@ -1073,6 +1111,7 @@
|
|||||||
const issue = issues.find(i => i.id === id);
|
const issue = issues.find(i => i.id === id);
|
||||||
if (issue) {
|
if (issue) {
|
||||||
await set(ref(db, `manutencao/${id}`), { ...issue, status: 'Resolvido' });
|
await set(ref(db, `manutencao/${id}`), { ...issue, status: 'Resolvido' });
|
||||||
|
sendSystemNotification(`A manutenção "${issue.title}" foi concluída com sucesso.`, 'success', 'todos');
|
||||||
showNotification('Ocorrência resolvida com sucesso');
|
showNotification('Ocorrência resolvida com sucesso');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1108,6 +1147,12 @@
|
|||||||
desc: `Reserva por ${bookingData.resident}`
|
desc: `Reserva por ${bookingData.resident}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendSystemNotification(`Nova reserva: ${bookingData.facilityName} a ${bookingData.date}`, 'info', 'admin');
|
||||||
|
if (userRole !== 'admin') {
|
||||||
|
sendSystemNotification(`A sua reserva para ${bookingData.facilityName} foi confirmada.`, 'success', currentUserId);
|
||||||
|
}
|
||||||
|
|
||||||
showNotification(`Reserva confirmada para ${bookingData.facilityName}`);
|
showNotification(`Reserva confirmada para ${bookingData.facilityName}`);
|
||||||
handleCloseModal();
|
handleCloseModal();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1131,6 +1176,10 @@
|
|||||||
date: new Date().toISOString().split('T')[0],
|
date: new Date().toISOString().split('T')[0],
|
||||||
status: 'Emitida'
|
status: 'Emitida'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
sendSystemNotification(`Foi emitida uma nova fatura instantânea no valor de ${Number(resident.pending).toFixed(2)}€`, 'warning', resident.id);
|
||||||
|
sendSystemNotification(`Fatura instantânea gerada para a fração ${resident.unit} no valor de ${Number(resident.pending).toFixed(2)}€`, 'info', 'admin');
|
||||||
|
|
||||||
showNotification(`Fatura instantânea gerada para a fração ${resident.unit}`, 'success');
|
showNotification(`Fatura instantânea gerada para a fração ${resident.unit}`, 'success');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Erro ao faturar:", error);
|
console.error("Erro ao faturar:", error);
|
||||||
@@ -1699,7 +1748,14 @@
|
|||||||
<div className={`mt-1 w-2 h-2 rounded-full flex-shrink-0 ${notif.type === 'info' ? 'bg-blue-500' : notif.type === 'success' ? 'bg-green-500' : 'bg-red-500'}`}></div>
|
<div className={`mt-1 w-2 h-2 rounded-full flex-shrink-0 ${notif.type === 'info' ? 'bg-blue-500' : notif.type === 'success' ? 'bg-green-500' : 'bg-red-500'}`}></div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-slate-700 dark:text-slate-200 leading-tight">{notif.message}</p>
|
<p className="text-sm text-slate-700 dark:text-slate-200 leading-tight">{notif.message}</p>
|
||||||
<p className="text-xs text-slate-400 dark:text-slate-500 mt-1">{notif.time}</p>
|
<div className="flex justify-between items-center mt-1">
|
||||||
|
<p className="text-xs text-slate-400 dark:text-slate-500">{notif.time}</p>
|
||||||
|
{!notif.read && (
|
||||||
|
<button onClick={() => handleMarkAsRead(notif.id)} className="text-[10px] text-blue-600 dark:text-blue-400 hover:underline">
|
||||||
|
Marcar como lida
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1846,6 +1902,8 @@
|
|||||||
<button onClick={() => {
|
<button onClick={() => {
|
||||||
if(window.confirm('Aprovar este morador?')) {
|
if(window.confirm('Aprovar este morador?')) {
|
||||||
set(ref(db, `condominos/${req.id}/status`), 'aprovado');
|
set(ref(db, `condominos/${req.id}/status`), 'aprovado');
|
||||||
|
sendSystemNotification(`O registo do morador ${req.name} (${req.unit || ''}) foi aprovado.`, 'success', 'admin');
|
||||||
|
sendSystemNotification(`A sua conta foi aprovada pela administração! Bem-vindo(a).`, 'success', req.id);
|
||||||
showNotification('Morador aprovado com sucesso!', 'success');
|
showNotification('Morador aprovado com sucesso!', 'success');
|
||||||
}
|
}
|
||||||
}} className="p-2 bg-green-100 text-green-600 rounded hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400" title="Aprovar">
|
}} className="p-2 bg-green-100 text-green-600 rounded hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400" title="Aprovar">
|
||||||
|
|||||||
Reference in New Issue
Block a user