notificações

This commit is contained in:
2026-04-30 10:42:56 +01:00
parent 0fe52b0625
commit 1f96a9bfae

View File

@@ -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) {
@@ -555,6 +547,14 @@
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');
sessionStorage.setItem('condo_user_name', data.name); sessionStorage.setItem('condo_user_name', data.name);
@@ -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">