linguagem

This commit is contained in:
2026-05-08 10:41:02 +01:00
parent a7f8e39aae
commit c16fda3fe0
5 changed files with 775 additions and 156 deletions

View File

@@ -18,9 +18,8 @@
dark: {
bg: '#0f172a',
surface: '#1e293b',
card: '#334155',
border: '#475569',
text: '#f1f5f9',
border: '#334155',
card: '#1e293b',
mute: '#94a3b8'
}
}
@@ -28,6 +27,24 @@
}
}
</script>
<!-- Google Translate Script -->
<script type="text/javascript">
function googleTranslateElementInit() {
new google.translate.TranslateElement({pageLanguage: 'pt', includedLanguages: 'pt,en,es,fr', autoDisplay: false}, 'google_translate_element');
}
</script>
<script type="text/javascript" src="https://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"></script>
<style>
/* Esconder a barra e o widget do Google Translate nativos */
.skiptranslate iframe, .goog-te-banner-frame { display: none !important; }
body { top: 0px !important; }
#google_translate_element { display: none !important; }
.goog-tooltip { display: none !important; }
.goog-tooltip:hover { display: none !important; }
.goog-text-highlight { background-color: transparent !important; border: none !important; box-shadow: none !important; }
</style>
<script type="importmap">
{
@@ -490,7 +507,7 @@
userId = 'admin_001';
} else {
const residentUser = residents.find(r => r.email && r.email.toLowerCase() === email.toLowerCase());
if (residentUser && (password === residentUser.password || password === '1234')) {
if (residentUser && (password === residentUser.password || (!residentUser.password && password === residentUser.contact) || password === '1234')) {
role = residentUser.role || 'morador';
userName = residentUser.name + (residentUser.unit && residentUser.unit !== 'Pendente' ? ` (${residentUser.unit})` : '');
userId = residentUser.id || userId;
@@ -735,8 +752,12 @@
} else if (type === 'emitir_fatura') {
setFormData(initialFaturaForm);
} else if (type === 'booking') {
const baseForm = initialBookingForm;
const baseForm = { ...initialBookingForm };
if (defaultFacility) baseForm.facility = defaultFacility;
// Preenche sempre o nome do utilizador logado por defeito
baseForm.resident = currentUserName;
setFormData(baseForm);
}
};
@@ -1332,6 +1353,43 @@
setFormData(prev => ({ ...prev, [field]: value }));
};
const [passwordData, setPasswordData] = useState({ current: '', new: '', confirm: '' });
const handlePasswordChange = (field, value) => {
setPasswordData(prev => ({ ...prev, [field]: value }));
};
const handleSavePassword = async () => {
if (passwordData.new !== passwordData.confirm) {
showNotification('As novas palavras-passe não coincidem.', 'error');
return;
}
if (passwordData.new.length < 4) {
showNotification('A nova palavra-passe deve ter pelo menos 4 caracteres.', 'error');
return;
}
if (isMorador) {
const currentUserData = residents.find(r => r.id === currentUserId);
const currentPassword = currentUserData.password || currentUserData.contact || '1234';
if (passwordData.current !== currentPassword) {
showNotification('A palavra-passe atual está incorreta.', 'error');
return;
}
try {
await set(ref(db, `condominos/${currentUserData.id}/password`), passwordData.new);
showNotification('Palavra-passe alterada com sucesso!', 'success');
setPasswordData({ current: '', new: '', confirm: '' });
sendSystemNotification('Um utilizador alterou a sua palavra-passe.', 'info', 'admin');
} catch (error) {
console.error("Erro ao alterar palavra-passe:", error);
showNotification('Erro ao alterar a palavra-passe.', 'error');
}
} else {
showNotification('A conta de administrador usa o Firebase Auth para gerir passwords.', 'info');
}
};
const handleSave = async () => {
if (isMorador) {
const currentUserData = residents.find(r => r.id === currentUserId);
@@ -1438,16 +1496,13 @@
</div>
<div className="space-y-4">
<InputGroup label="Palavra-passe Atual" type="password" placeholder="••••••••" />
<InputGroup label="Palavra-passe Atual" type="password" placeholder="••••••••" value={passwordData.current} onChange={(e) => handlePasswordChange('current', e.target.value)} />
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<InputGroup label="Nova Palavra-passe" type="password" placeholder="Min. 8 caracteres" />
<InputGroup label="Confirmar Nova Palavra-passe" type="password" placeholder="Confirmar" />
<InputGroup label="Nova Palavra-passe" type="password" placeholder="Min. 8 caracteres" value={passwordData.new} onChange={(e) => handlePasswordChange('new', e.target.value)} />
<InputGroup label="Confirmar Nova Palavra-passe" type="password" placeholder="Confirmar" value={passwordData.confirm} onChange={(e) => handlePasswordChange('confirm', e.target.value)} />
</div>
<div className="flex justify-end mt-6">
<button onClick={() => {
showNotification('Segurança atualizada com sucesso!', 'success');
sendSystemNotification('Um utilizador alterou a palavra-passe.', 'info', 'admin');
}} className="bg-slate-800 dark:bg-slate-700 text-white px-6 py-2 rounded-lg font-medium hover:bg-slate-900 dark:hover:bg-slate-600 shadow-sm transition-colors">
<button onClick={handleSavePassword} className="bg-slate-800 dark:bg-slate-700 text-white px-6 py-2 rounded-lg font-medium hover:bg-slate-900 dark:hover:bg-slate-600 shadow-sm transition-colors">
Atualizar Segurança
</button>
</div>
@@ -1504,6 +1559,43 @@
</div>
</div>
<div>
<h4 className="text-sm font-bold text-slate-700 dark:text-slate-300 mb-3">Idioma da Aplicação</h4>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{[
{ code: 'pt', label: 'Português', flag: '🇵🇹' },
{ code: 'en', label: 'English', flag: '🇬🇧' },
{ code: 'es', label: 'Español', flag: '🇪🇸' },
{ code: 'fr', label: 'Français', flag: '🇫🇷' }
].map(lang => {
const match = document.cookie.match(/googtrans=\/pt\/([a-z]{2})/);
const activeLang = match ? match[1] : 'pt';
const isActive = activeLang === lang.code;
return (
<div
key={lang.code}
onClick={() => {
if(lang.code === 'pt') {
document.cookie = "googtrans=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
document.cookie = "googtrans=; expires=Thu, 01 Jan 1970 00:00:00 UTC; domain=" + window.location.hostname + "; path=/;";
} else {
document.cookie = `googtrans=/pt/${lang.code}; path=/;`;
document.cookie = `googtrans=/pt/${lang.code}; domain=${window.location.hostname}; path=/;`;
}
window.location.reload();
}}
className={`border-2 p-3 rounded-lg text-center cursor-pointer transition-colors ${isActive ? 'border-blue-500 bg-blue-50 dark:bg-blue-900/30' : 'border-slate-200 dark:border-dark-border bg-white dark:bg-dark-card hover:bg-slate-50 dark:hover:bg-dark-surface'}`}
>
<div className="text-2xl mb-1">{lang.flag}</div>
<div className="text-xs font-bold text-slate-700 dark:text-slate-300">{lang.label}</div>
</div>
);
})}
</div>
<p className="text-xs text-slate-500 dark:text-slate-400 mt-2">A tradução é efetuada automaticamente e afetará toda a aplicação após recarregar a página.</p>
</div>
<div>
<h4 className="text-sm font-bold text-slate-700 mb-3">Aparência</h4>
<div className="grid grid-cols-3 gap-4">
@@ -2426,7 +2518,7 @@
<InputGroup label="Data" type="date" name="date" value={formData.date || ''} onChange={handleInputChange} required />
<InputGroup label="Horário" name="time" value={formData.time || ''} onChange={handleInputChange} placeholder="Ex: 14:00 - 16:00" required />
</div>
<InputGroup label="Reservado para (Condómino)" name="resident" value={formData.resident || ''} onChange={handleInputChange} placeholder="Nome do residente" required />
<InputGroup label="Reservado para (Condómino)" name="resident" value={formData.resident || ''} onChange={handleInputChange} placeholder="Nome do residente" required disabled={userRole !== 'admin'} />
<div className="bg-slate-50 dark:bg-dark-card p-4 rounded-lg border border-slate-200 dark:border-dark-border mt-2 mb-4 flex justify-between items-center transition-colors">
<span className="text-sm font-medium text-slate-600 dark:text-slate-300">Custo Estimado:</span>
@@ -2498,6 +2590,7 @@
</ErrorBoundary>
);
</script>
<div id="google_translate_element"></div>
<!-- Firebase configs moved to top in React Module -->
</body>
</html>