feat: Replace alert() with flash notifications and add bell notification

- Replace all alert() calls with showNotification() for consistent UX
- Add UserNotification creation when admin proposes changes
- User sees notification in bell icon with link to review changes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-02-01 16:00:58 +01:00
parent 193d5ad8e3
commit 6370abb24f
2 changed files with 69 additions and 23 deletions

View File

@ -15,7 +15,7 @@ from flask_login import login_required, current_user
from . import bp
from database import (
SessionLocal, MembershipApplication, CompanyDataRequest,
Company, Category, User
Company, Category, User, UserNotification
)
logger = logging.getLogger(__name__)
@ -438,6 +438,18 @@ def admin_membership_propose_changes(app_id):
application.registry_data = registry_data
application.registry_source = registry_data.get('source', 'KRS')
# Create notification for user
notification = UserNotification(
user_id=application.user_id,
title='Propozycja zmian w deklaracji',
message=f'Administrator zaproponował aktualizację danych firmy "{application.company_name}" na podstawie rejestru {registry_data.get("source", "KRS")}. Przejrzyj i zaakceptuj lub odrzuć zmiany.',
notification_type='alert',
related_type='membership_application',
related_id=app_id,
action_url=f'/membership/review-changes/{app_id}'
)
db.add(notification)
db.commit()
logger.info(

View File

@ -898,6 +898,33 @@ const appNip = '{{ application.nip or "" }}';
const csrfToken = '{{ csrf_token() }}';
let registryData = null;
// Ładne powiadomienia zamiast alert()
function showNotification(message, type = 'info') {
// type: 'success', 'error', 'warning', 'info'
let container = document.querySelector('.flash-messages');
if (!container) {
container = document.createElement('div');
container.className = 'flash-messages';
container.setAttribute('role', 'alert');
container.setAttribute('aria-live', 'polite');
document.body.appendChild(container);
}
const flash = document.createElement('div');
flash.className = `flash flash-${type}`;
flash.innerHTML = `
<span>${message}</span>
<button class="flash-close" onclick="this.parentElement.remove()" aria-label="Close">&times;</button>
`;
container.appendChild(flash);
// Auto-dismiss after 5 seconds
setTimeout(() => {
flash.style.animation = 'slideOut 0.3s ease-out';
setTimeout(() => flash.remove(), 300);
}, 5000);
}
function setLookupStatus(message, type) {
const statusEl = document.getElementById('lookupStatus');
if (statusEl) {
@ -910,7 +937,7 @@ function setLookupStatus(message, type) {
// Pobieranie danych z rejestru
async function lookupRegistry() {
if (!appNip) {
alert('Brak NIP w deklaracji');
showNotification('Brak NIP w deklaracji', 'warning');
return;
}
@ -1021,7 +1048,7 @@ async function lookupRegistry() {
function openProposeModal() {
if (!registryData) {
alert('Brak danych do zaproponowania');
showNotification('Najpierw pobierz dane z rejestru', 'warning');
return;
}
@ -1062,7 +1089,7 @@ function openProposeModal() {
async function proposeChanges() {
if (!registryData) {
alert('Brak danych do zaproponowania');
showNotification('Najpierw pobierz dane z rejestru', 'warning');
return;
}
@ -1080,13 +1107,14 @@ async function proposeChanges() {
const result = await response.json();
if (result.success) {
alert('Zmiany zostały zaproponowane użytkownikowi. Status deklaracji zmieniono na "Oczekuje na akceptację użytkownika".');
location.reload();
showNotification('Propozycja zmian została wysłana do użytkownika. Oczekuje na akceptację.', 'success');
closeModal('proposeModal');
setTimeout(() => location.reload(), 1500);
} else {
alert(result.error || 'Błąd');
showNotification(result.error || 'Wystąpił błąd', 'error');
}
} catch (e) {
alert('Błąd połączenia');
showNotification('Błąd połączenia z serwerem', 'error');
}
}
@ -1114,12 +1142,13 @@ async function startReview() {
});
const result = await response.json();
if (result.success) {
location.reload();
showNotification('Rozpoczęto rozpatrywanie deklaracji', 'success');
setTimeout(() => location.reload(), 1000);
} else {
alert(result.error || 'Błąd');
showNotification(result.error || 'Wystąpił błąd', 'error');
}
} catch (e) {
alert('Błąd połączenia');
showNotification('Błąd połączenia z serwerem', 'error');
}
}
@ -1135,20 +1164,21 @@ async function approve() {
});
const result = await response.json();
if (result.success) {
alert(`Zatwierdzono! Nr członkowski: ${result.member_number}`);
location.reload();
showNotification(`Deklaracja zatwierdzona! Nr członkowski: ${result.member_number}`, 'success');
closeModal('approveModal');
setTimeout(() => location.reload(), 1500);
} else {
alert(result.error || 'Błąd');
showNotification(result.error || 'Wystąpił błąd', 'error');
}
} catch (e) {
alert('Błąd połączenia');
showNotification('Błąd połączenia z serwerem', 'error');
}
}
async function reject() {
const comment = document.getElementById('rejectComment').value.trim();
if (!comment) {
alert('Podaj powód odrzucenia');
showNotification('Podaj powód odrzucenia', 'warning');
return;
}
@ -1160,19 +1190,21 @@ async function reject() {
});
const result = await response.json();
if (result.success) {
location.reload();
showNotification('Deklaracja została odrzucona', 'info');
closeModal('rejectModal');
setTimeout(() => location.reload(), 1500);
} else {
alert(result.error || 'Błąd');
showNotification(result.error || 'Wystąpił błąd', 'error');
}
} catch (e) {
alert('Błąd połączenia');
showNotification('Błąd połączenia z serwerem', 'error');
}
}
async function requestChanges() {
const comment = document.getElementById('changesComment').value.trim();
if (!comment) {
alert('Opisz wymagane poprawki');
showNotification('Opisz wymagane poprawki', 'warning');
return;
}
@ -1184,12 +1216,14 @@ async function requestChanges() {
});
const result = await response.json();
if (result.success) {
location.reload();
showNotification('Prośba o poprawki została wysłana', 'success');
closeModal('changesModal');
setTimeout(() => location.reload(), 1500);
} else {
alert(result.error || 'Błąd');
showNotification(result.error || 'Wystąpił błąd', 'error');
}
} catch (e) {
alert('Błąd połączenia');
showNotification('Błąd połączenia z serwerem', 'error');
}
}