fix: decode HTML entities in social audit, replace browser dialogs with modals
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
- Fix checkmarks showing as ✓ by using Unicode ✓/✗ directly - Decode HTML entities (' &) from og:meta in enricher results - Replace native confirm()/alert() with styled modal dialogs and toasts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
23b8815e10
commit
ce0a6863d2
@ -21,6 +21,7 @@ Author: Claude Code
|
||||
Date: 2025-12-29
|
||||
"""
|
||||
|
||||
import html as html_module
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
@ -1096,7 +1097,12 @@ class SocialProfileEnricher:
|
||||
enricher = enrichers.get(platform)
|
||||
if enricher:
|
||||
try:
|
||||
return enricher(url)
|
||||
result = enricher(url)
|
||||
# Decode HTML entities in all string values (og:meta often contains & ' etc.)
|
||||
for key, val in result.items():
|
||||
if isinstance(val, str):
|
||||
result[key] = html_module.unescape(val)
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to enrich {platform} profile {url}: {e}")
|
||||
return {}
|
||||
|
||||
@ -694,7 +694,7 @@
|
||||
<div class="profile-checklist">
|
||||
{% if p.has_profile_photo is not none %}
|
||||
<span class="check-item {{ 'ok' if p.has_profile_photo else 'missing' }}">
|
||||
{{ '✓' if p.has_profile_photo else '✗' }} Zdjęcie profilowe
|
||||
{{ '✓' if p.has_profile_photo else '✗' }} Zdjęcie profilowe
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="check-item unknown">? Zdjęcie profilowe</span>
|
||||
@ -702,7 +702,7 @@
|
||||
|
||||
{% if p.has_cover_photo is not none %}
|
||||
<span class="check-item {{ 'ok' if p.has_cover_photo else 'missing' }}">
|
||||
{{ '✓' if p.has_cover_photo else '✗' }} Zdjęcie w tle
|
||||
{{ '✓' if p.has_cover_photo else '✗' }} Zdjęcie w tle
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="check-item unknown">? Zdjęcie w tle</span>
|
||||
@ -710,7 +710,7 @@
|
||||
|
||||
{% if p.has_bio is not none %}
|
||||
<span class="check-item {{ 'ok' if p.has_bio else 'missing' }}">
|
||||
{{ '✓' if p.has_bio else '✗' }} Opis / bio
|
||||
{{ '✓' if p.has_bio else '✗' }} Opis / bio
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="check-item unknown">? Opis / bio</span>
|
||||
|
||||
@ -323,6 +323,41 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- Confirm Modal -->
|
||||
<div class="modal-overlay" id="confirmModal">
|
||||
<div class="modal-box">
|
||||
<div class="modal-icon" id="modalIcon">
|
||||
<svg width="28" height="28" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"/></svg>
|
||||
</div>
|
||||
<h3 class="modal-title" id="modalTitle"></h3>
|
||||
<p class="modal-message" id="modalMessage"></p>
|
||||
<p class="modal-detail" id="modalDetail"></p>
|
||||
<div class="modal-actions" id="modalActions"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="toastContainer" style="position: fixed; top: 80px; right: 20px; z-index: 3000; display: flex; flex-direction: column; gap: 10px;"></div>
|
||||
|
||||
<style>
|
||||
.modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 2000; justify-content: center; align-items: center; animation: modalFadeIn 0.2s ease; }
|
||||
.modal-overlay.active { display: flex; }
|
||||
.modal-box { background: var(--surface); border-radius: var(--radius-lg); padding: var(--spacing-xl); max-width: 440px; width: 90%; text-align: center; box-shadow: 0 20px 40px rgba(0,0,0,0.2); animation: modalSlideUp 0.3s ease; }
|
||||
@keyframes modalFadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
@keyframes modalSlideUp { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
|
||||
.modal-icon { width: 56px; height: 56px; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto var(--spacing-md); }
|
||||
.modal-icon.warn { background: #fef3c7; color: #f59e0b; }
|
||||
.modal-icon.danger { background: #fee2e2; color: #ef4444; }
|
||||
.modal-icon.success { background: #dcfce7; color: #22c55e; }
|
||||
.modal-title { font-size: var(--font-size-lg); font-weight: 600; color: var(--text-primary); margin-bottom: var(--spacing-sm); }
|
||||
.modal-message { color: var(--text-secondary); margin-bottom: var(--spacing-xs); line-height: 1.5; }
|
||||
.modal-detail { font-size: var(--font-size-sm); color: var(--text-secondary); margin-bottom: var(--spacing-lg); }
|
||||
.modal-actions { display: flex; gap: var(--spacing-sm); justify-content: center; }
|
||||
.toast { padding: 12px 20px; border-radius: var(--radius); background: var(--surface); border-left: 4px solid var(--primary); box-shadow: 0 4px 12px rgba(0,0,0,0.15); display: flex; align-items: center; gap: 10px; animation: toastIn 0.3s ease; max-width: 400px; }
|
||||
.toast.success { border-left-color: #22c55e; }
|
||||
.toast.error { border-left-color: #ef4444; }
|
||||
@keyframes toastIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
|
||||
@keyframes toastOut { from { opacity: 1; } to { opacity: 0; } }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
@ -333,9 +368,60 @@ function toggleCompany(header) {
|
||||
arrow.classList.toggle('open');
|
||||
}
|
||||
|
||||
function approveChanges() {
|
||||
if (!confirm('Czy na pewno chcesz zatwierdzić i zapisać zebrane dane do bazy?\n\nTa operacja zaktualizuje {{ summary.profiles_with_changes }} profili w {{ summary.companies_with_changes }} firmach.')) return;
|
||||
// Toast
|
||||
function showToast(message, type, duration) {
|
||||
type = type || 'info'; duration = duration || 4000;
|
||||
var icons = { success: '✓', error: '✕', warning: '⚠', info: 'ℹ' };
|
||||
var container = document.getElementById('toastContainer');
|
||||
var toast = document.createElement('div');
|
||||
toast.className = 'toast ' + type;
|
||||
toast.innerHTML = '<span style="font-size:1.2em">' + (icons[type]||'ℹ') + '</span><span>' + message + '</span>';
|
||||
container.appendChild(toast);
|
||||
setTimeout(function() { toast.style.animation = 'toastOut 0.3s ease forwards'; setTimeout(function() { toast.remove(); }, 300); }, duration);
|
||||
}
|
||||
|
||||
// Modal
|
||||
function showModal(opts) {
|
||||
var icon = document.getElementById('modalIcon');
|
||||
icon.className = 'modal-icon ' + (opts.iconType || 'warn');
|
||||
icon.innerHTML = opts.iconSvg || '<svg width="28" height="28" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"/></svg>';
|
||||
document.getElementById('modalTitle').textContent = opts.title;
|
||||
document.getElementById('modalMessage').innerHTML = opts.message;
|
||||
document.getElementById('modalDetail').textContent = opts.detail || '';
|
||||
var actions = document.getElementById('modalActions');
|
||||
actions.innerHTML = '';
|
||||
(opts.buttons || []).forEach(function(b) {
|
||||
var btn = document.createElement('button');
|
||||
btn.className = 'btn ' + (b.cls || 'btn-outline');
|
||||
btn.textContent = b.label;
|
||||
btn.onclick = function() { closeModal(); if (b.action) b.action(); };
|
||||
actions.appendChild(btn);
|
||||
});
|
||||
document.getElementById('confirmModal').classList.add('active');
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('confirmModal').classList.remove('active');
|
||||
}
|
||||
|
||||
document.getElementById('confirmModal').addEventListener('click', function(e) {
|
||||
if (e.target === this) closeModal();
|
||||
});
|
||||
|
||||
function approveChanges() {
|
||||
showModal({
|
||||
title: 'Zatwierdź zmiany',
|
||||
iconType: 'warn',
|
||||
message: 'Czy na pewno chcesz zatwierdzić i zapisać zebrane dane do bazy?',
|
||||
detail: 'Ta operacja zaktualizuje {{ summary.profiles_with_changes }} profili w {{ summary.companies_with_changes }} firmach.',
|
||||
buttons: [
|
||||
{ label: 'Anuluj', cls: 'btn-outline' },
|
||||
{ label: 'Zatwierdź i zapisz', cls: 'btn-primary', action: doApprove }
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
function doApprove() {
|
||||
var approveBtn = document.getElementById('approveBtn');
|
||||
var discardBtn = document.getElementById('discardBtn');
|
||||
if (approveBtn) { approveBtn.disabled = true; approveBtn.textContent = 'Zapisywanie...'; }
|
||||
@ -348,24 +434,36 @@ function approveChanges() {
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
if (data.status === 'approved') {
|
||||
alert('Zatwierdzone! Zaktualizowano ' + data.applied + ' profili.' + (data.errors > 0 ? ' (' + data.errors + ' błędów)' : ''));
|
||||
location.reload();
|
||||
showToast('Zaktualizowano ' + data.applied + ' profili' + (data.errors > 0 ? ' (' + data.errors + ' błędów)' : ''), 'success', 5000);
|
||||
setTimeout(function() { location.reload(); }, 1500);
|
||||
} else {
|
||||
alert('Błąd: ' + (data.error || 'Nieznany błąd'));
|
||||
showToast('Błąd: ' + (data.error || 'Nieznany błąd'), 'error');
|
||||
if (approveBtn) { approveBtn.disabled = false; approveBtn.textContent = 'Zatwierdź'; }
|
||||
if (discardBtn) discardBtn.disabled = false;
|
||||
}
|
||||
})
|
||||
.catch(function(e) {
|
||||
alert('Błąd: ' + e.message);
|
||||
showToast('Błąd połączenia: ' + e.message, 'error');
|
||||
if (approveBtn) { approveBtn.disabled = false; approveBtn.textContent = 'Zatwierdź'; }
|
||||
if (discardBtn) discardBtn.disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
function discardChanges() {
|
||||
if (!confirm('Czy na pewno chcesz ODRZUCIĆ wszystkie zebrane dane?\n\nBaza danych nie zostanie zmieniona.')) return;
|
||||
showModal({
|
||||
title: 'Odrzuć zmiany',
|
||||
iconType: 'danger',
|
||||
iconSvg: '<svg width="28" height="28" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>',
|
||||
message: 'Czy na pewno chcesz odrzucić wszystkie zebrane dane?',
|
||||
detail: 'Baza danych nie zostanie zmieniona.',
|
||||
buttons: [
|
||||
{ label: 'Anuluj', cls: 'btn-outline' },
|
||||
{ label: 'Odrzuć', cls: 'btn-danger', action: doDiscard }
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
function doDiscard() {
|
||||
fetch('{{ url_for("admin.admin_social_audit_enrichment_discard") }}', {
|
||||
method: 'POST',
|
||||
headers: {'X-CSRFToken': '{{ csrf_token() }}'},
|
||||
@ -373,8 +471,8 @@ function discardChanges() {
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
if (data.status === 'discarded') {
|
||||
alert('Odrzucono ' + data.count + ' zmian. Baza danych nie została zmieniona.');
|
||||
window.location.href = '{{ url_for("admin.admin_social_audit") }}';
|
||||
showToast('Odrzucono ' + data.count + ' zmian. Baza bez zmian.', 'success');
|
||||
setTimeout(function() { window.location.href = '{{ url_for("admin.admin_social_audit") }}'; }, 1500);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user