/** * NIS2 Agile — Sistema Segnalazioni & Risoluzione AI * * Adattato da alltax.it/docs/sistema-segnalazioni-standard.html * * Componenti: * - FAB (Floating Action Button) rosso bottom-right * - Modal fase 1: inserimento segnalazione * - Modal fase 2: risposta AI + conferma risoluzione / password gate * - Tab "Le mie segnalazioni" */ (function () { 'use strict'; // ── Config ──────────────────────────────────────────────────────────────── const RESOLVE_LABEL_YES = 'Sì, problema risolto'; const RESOLVE_LABEL_NO = 'No, il problema persiste'; const TIPO_LABELS = { bug: '🐛 Bug / Errore', ux: '🎨 Miglioramento interfaccia', funzionalita: '✨ Nuova funzionalità', domanda: '❓ Domanda / Supporto', altro: '📌 Altro', }; const PRIORITA_LABELS = { alta: '🔴 Alta', media: '🟡 Media', bassa: '🟢 Bassa', }; const STATUS_LABELS = { aperto: { label: 'Aperto', color: '#64748b' }, in_lavorazione:{ label: 'In lavorazione', color: '#f59e0b' }, risolto: { label: 'Risolto', color: '#22c55e' }, chiuso: { label: 'Chiuso', color: '#3b82f6' }, }; // ── State ───────────────────────────────────────────────────────────────── let _currentReportId = null; let _attachmentBase64 = null; let _myReports = []; // ── Init ────────────────────────────────────────────────────────────────── /** * Inizializza il sistema di feedback. * Chiamato da common.js dopo la verifica autenticazione. */ window.initFeedbackFab = function () { if (!localStorage.getItem('nis2_access_token')) return; if (document.getElementById('feedback-fab')) return; // già presente _injectFab(); _injectModal(); _bindEvents(); }; // ── FAB ─────────────────────────────────────────────────────────────────── function _injectFab() { const fab = document.createElement('button'); fab.id = 'feedback-fab'; fab.type = 'button'; fab.title = 'Segnala un problema o suggerisci un miglioramento'; fab.innerHTML = ''; fab.setAttribute('aria-label', 'Segnala problema'); document.body.appendChild(fab); } // ── Modal HTML ──────────────────────────────────────────────────────────── function _injectModal() { const wrapper = document.createElement('div'); wrapper.id = 'feedback-modal-wrapper'; wrapper.innerHTML = _buildModalHtml(); document.body.appendChild(wrapper); } function _buildModalHtml() { const tipoOptions = Object.entries(TIPO_LABELS) .map(([v, l]) => ``) .join(''); const prioritaOptions = Object.entries(PRIORITA_LABELS) .map(([v, l]) => ``) .join(''); return `
`; } // ── Events ──────────────────────────────────────────────────────────────── function _bindEvents() { // Apri modal document.getElementById('feedback-fab').addEventListener('click', _openModal); // Chiudi document.getElementById('feedback-close').addEventListener('click', _closeModal); document.getElementById('feedback-cancel')?.addEventListener('click', _closeModal); document.getElementById('feedback-overlay').addEventListener('click', (e) => { if (e.target.id === 'feedback-overlay') _closeModal(); }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape') _closeModal(); }); // Tabs document.querySelectorAll('.feedback-tab').forEach(btn => { btn.addEventListener('click', () => _switchTab(btn.dataset.tab)); }); // Textarea charcount document.getElementById('feedback-descrizione').addEventListener('input', function () { document.getElementById('feedback-charcount').textContent = this.value.length; }); // File upload const fileInput = document.getElementById('feedback-file-input'); const dropArea = document.getElementById('feedback-drop-area'); fileInput.addEventListener('change', () => _handleFile(fileInput.files[0])); dropArea.addEventListener('dragover', (e) => { e.preventDefault(); dropArea.classList.add('feedback-drop-active'); }); dropArea.addEventListener('dragleave', () => dropArea.classList.remove('feedback-drop-active')); dropArea.addEventListener('drop', (e) => { e.preventDefault(); dropArea.classList.remove('feedback-drop-active'); const f = e.dataTransfer.files[0]; if (f && f.type.startsWith('image/')) _handleFile(f); }); document.getElementById('feedback-remove-img').addEventListener('click', _removeAttachment); // Submit form document.getElementById('feedback-form').addEventListener('submit', _onSubmit); // Fase 2 actions document.getElementById('feedback-resolve-yes').addEventListener('click', _onResolveYes); document.getElementById('feedback-resolve-no').addEventListener('click', _onResolveNo); document.getElementById('feedback-resolve-confirm').addEventListener('click', _onResolveConfirm); } // ── Modal open/close ────────────────────────────────────────────────────── function _openModal() { document.getElementById('feedback-overlay').classList.add('active'); document.getElementById('feedback-page-url').value = window.location.href; document.getElementById('feedback-descrizione').focus(); } function _closeModal() { document.getElementById('feedback-overlay').classList.remove('active'); // Reset stato dopo breve delay setTimeout(_resetModal, 300); } function _resetModal() { _currentReportId = null; _attachmentBase64 = null; document.getElementById('feedback-form').reset(); document.getElementById('feedback-charcount').textContent = '0'; document.getElementById('feedback-phase1-error').style.display = 'none'; _removeAttachment(); _showPhase(1); _switchTab('new'); } function _switchTab(tab) { document.querySelectorAll('.feedback-tab').forEach(b => b.classList.toggle('active', b.dataset.tab === tab)); document.getElementById('feedback-phase-1').style.display = tab === 'new' ? '' : 'none'; document.getElementById('feedback-phase-2').style.display = 'none'; document.getElementById('feedback-tab-mine').style.display = tab === 'mine' ? '' : 'none'; if (tab === 'mine') _loadMyReports(); } function _showPhase(n) { document.getElementById('feedback-phase-1').style.display = n === 1 ? '' : 'none'; document.getElementById('feedback-phase-2').style.display = n === 2 ? '' : 'none'; } // ── File handling ───────────────────────────────────────────────────────── function _handleFile(file) { if (!file) return; if (file.size > 1.5 * 1024 * 1024) { _showPhaseError('Immagine troppo grande (max 1.5 MB).'); return; } const reader = new FileReader(); reader.onload = (e) => { _attachmentBase64 = e.target.result; document.getElementById('feedback-preview-img').src = e.target.result; document.getElementById('feedback-preview').style.display = ''; }; reader.readAsDataURL(file); } function _removeAttachment() { _attachmentBase64 = null; document.getElementById('feedback-file-input').value = ''; document.getElementById('feedback-preview').style.display = 'none'; document.getElementById('feedback-preview-img').src = ''; } // ── Submit ──────────────────────────────────────────────────────────────── async function _onSubmit(e) { e.preventDefault(); const tipo = document.getElementById('feedback-tipo').value; const priorita = document.getElementById('feedback-priorita').value; const descrizione = document.getElementById('feedback-descrizione').value.trim(); const pageUrl = document.getElementById('feedback-page-url').value; if (descrizione.length < 10) { _showPhaseError('Descrizione troppo breve (minimo 10 caratteri).'); return; } _setSubmitting(true); const payload = { tipo, priorita, descrizione, page_url: pageUrl, attachment: _attachmentBase64 || null, }; try { const res = await api.post('/feedback/submit', payload); if (!res.success) { _showPhaseError(res.message || 'Errore durante l\'invio.'); _setSubmitting(false); return; } const report = res.data; _currentReportId = report.id; // Fase 2: mostra risposta AI const aiText = report.ai_risposta || 'La segnalazione è stata ricevuta. Il team tecnico la prenderà in carico al più presto.'; document.getElementById('feedback-ai-response').textContent = aiText; document.getElementById('feedback-resolved-ok').style.display = 'none'; document.getElementById('feedback-password-gate').style.display = 'none'; document.getElementById('feedback-resolve-yes').style.display = ''; document.getElementById('feedback-resolve-no').style.display = ''; _showPhase(2); } catch (err) { _showPhaseError('Errore di rete. Riprova.'); } finally { _setSubmitting(false); } } function _setSubmitting(loading) { document.getElementById('feedback-submit-text').style.display = loading ? 'none' : ''; document.getElementById('feedback-submit-loading').style.display = loading ? '' : 'none'; document.getElementById('feedback-submit').disabled = loading; } function _showPhaseError(msg) { const el = document.getElementById('feedback-phase1-error'); el.textContent = msg; el.style.display = ''; } // ── Fase 2 actions ──────────────────────────────────────────────────────── function _onResolveYes() { document.getElementById('feedback-password-gate').style.display = ''; document.getElementById('feedback-resolve-password').focus(); } function _onResolveNo() { // Ticket resta aperto — chiudi modal _closeModal(); if (window.showNotification) { showNotification('Segnalazione registrata. Il team la prenderà in carico.', 'info'); } } async function _onResolveConfirm() { const password = document.getElementById('feedback-resolve-password').value; const errEl = document.getElementById('feedback-password-error'); if (!password) { errEl.textContent = 'Inserisci la password di conferma.'; errEl.style.display = ''; return; } errEl.style.display = 'none'; const btn = document.getElementById('feedback-resolve-confirm'); btn.disabled = true; try { const res = await api.post(`/feedback/${_currentReportId}/resolve`, { password }); if (!res.success) { errEl.textContent = res.message || 'Password non corretta.'; errEl.style.display = ''; return; } document.getElementById('feedback-password-gate').style.display = 'none'; document.getElementById('feedback-resolve-yes').style.display = 'none'; document.getElementById('feedback-resolve-no').style.display = 'none'; document.getElementById('feedback-resolved-ok').style.display = ''; setTimeout(_closeModal, 2500); } catch (err) { errEl.textContent = 'Errore di rete. Riprova.'; errEl.style.display = ''; } finally { btn.disabled = false; } } // ── Le mie segnalazioni ─────────────────────────────────────────────────── async function _loadMyReports() { const container = document.getElementById('feedback-mine-list'); container.innerHTML = 'Nessuna segnalazione ancora. Usala per segnalare problemi o suggerire miglioramenti!
'; return; } container.innerHTML = _myReports.map(_renderReportRow).join(''); } catch (err) { container.innerHTML = 'Errore nel caricamento. Riprova.
'; } } function _renderReportRow(report) { const statusInfo = STATUS_LABELS[report.status] || { label: report.status, color: '#64748b' }; const tipoLabel = TIPO_LABELS[report.tipo] || report.tipo; const date = new Date(report.created_at).toLocaleDateString('it-IT', { day: '2-digit', month: 'short', year: 'numeric' }); const aiBox = report.ai_risposta ? `${_esc(report.descrizione.substring(0, 150))}${report.descrizione.length > 150 ? '…' : ''}
${aiBox}