`); try { const r = await fetch(`${URL}/documenti_valigia?id=eq.${id}&select=data,tipo,nome`, { headers: HR }); const [doc] = await r.json(); let rawData = doc.data; const isGz = rawData.startsWith('gz:'); if (isGz) rawData = rawData.slice(3); const binary = atob(rawData); let bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); if (isGz) bytes = new Uint8Array(await decomprimiGzip(bytes.buffer)); const mimeType = doc.tipo || tipo || 'application/octet-stream'; const blob = new Blob([bytes], { type: mimeType }); const urlApi = window.URL || window.webkitURL; const blobUrl = urlApi.createObjectURL(blob); win.location.href = blobUrl; setTimeout(() => urlApi.revokeObjectURL(blobUrl), 60000); } catch(e) { win.close(); toast('Errore: ' + (e.message || 'apertura fallita')); } } async function rinominaDocumento(id, nomeAttuale) { const nuovo = prompt('Nome documento:', nomeAttuale); if (!nuovo || nuovo.trim() === nomeAttuale) return; await patch(`/documenti_valigia?id=eq.${id}`, { nome: nuovo.trim() }); caricaDocumenti(); toast('✓ Rinominato'); } async function eliminaDocumento(id) { if (!confirm('Eliminare questo documento?')) return; await del(`/documenti_valigia?id=eq.${id}`); caricaDocumenti(); toast('Documento eliminato'); } // ── Modal: Nuova valigia ────────────────────────────────────── function setEmoji(e) { emojiScelta = e; document.querySelectorAll('#emojiPickerNew .emoji-opt').forEach(b => b.classList.toggle('active', b.textContent === e)); } function setEmojiEdit(e) { emojiEditScelta = e; document.querySelectorAll('#emojiPickerEdit .emoji-opt').forEach(b => b.classList.toggle('active', b.textContent === e)); } function apriModalNuova() { emojiScelta = '🧳'; document.getElementById('nomeNuovaValigia').value = ''; document.getElementById('dataViaggiaNuova').value = ''; renderEmojiPicker('emojiPickerNew', '🧳', 'setEmoji'); document.getElementById('modalNuova').classList.add('open'); setTimeout(() => document.getElementById('nomeNuovaValigia').focus(), 300); } function apriModalEditValigia(id) { valigiaEditId = id; const v = valigie.find(x => x.id === id); emojiEditScelta = v.emoji || '🧳'; document.getElementById('nomeEditValigia').value = v.nome; document.getElementById('dataViaggiaEdit').value = v.data_viaggio || ''; renderEmojiPicker('emojiPickerEdit', emojiEditScelta, 'setEmojiEdit'); document.getElementById('modalEditValigia').classList.add('open'); setTimeout(() => document.getElementById('nomeEditValigia').focus(), 300); } async function salvaEditValigia() { const nome = document.getElementById('nomeEditValigia').value.trim(); if (!nome) { toast('Inserisci il nome'); return; } const data_viaggio = document.getElementById('dataViaggiaEdit').value || null; await patch(`/valigie?id=eq.${valigiaEditId}`, { nome, emoji: emojiEditScelta, data_viaggio }); document.getElementById('modalEditValigia').classList.remove('open'); toast('✓ Valigia aggiornata'); if (valigiaCorrenteId === valigiaEditId) { document.getElementById('titoloHub').textContent = `${emojiEditScelta} ${nome}`; } caricaValigie(); } async function salvaValigia() { const nome = document.getElementById('nomeNuovaValigia').value.trim(); if (!nome) { toast('Inserisci il nome'); return; } const data_viaggio = document.getElementById('dataViaggiaNuova').value || null; await post('/valigie', { email_utente: userEmail, nome, emoji: emojiScelta, data_viaggio }); document.getElementById('modalNuova').classList.remove('open'); toast('✓ Valigia creata'); caricaValigie(); } // ── Modal: Aggiungi cosa ────────────────────────────────────── function apriModalAggiungi() { document.getElementById('nomeNuovaCosa').value = ''; document.getElementById('qtaNuovaCosa').value = '1'; document.getElementById('modalAggiungi').classList.add('open'); setTimeout(() => document.getElementById('nomeNuovaCosa').focus(), 300); } async function salvaCosa() { const nome = document.getElementById('nomeNuovaCosa').value.trim(); if (!nome) { toast('Inserisci il nome'); return; } const qta = parseInt(document.getElementById('qtaNuovaCosa').value) || 1; const res = await post('/cose_valigia', { email_utente: userEmail, nome, quantita: qta, completato: false, valigia_id: valigiaCorrenteId }); if (!res.ok) { toast('Errore nel salvataggio'); return; } document.getElementById('modalAggiungi').classList.remove('open'); caricaItems(); } // ── Elimina valigia ─────────────────────────────────────────── async function eliminaValigia(id) { const v = valigie.find(x => x.id === id); if (!confirm(`Eliminare la valigia "${v.nome}" e tutte le sue cose?`)) return; await del(`/cose_valigia?email_utente=eq.${userEmail}&valigia_id=eq.${id}`); await del(`/valigie?id=eq.${id}`); toast('Valigia eliminata'); caricaValigie(); } // ── Modal: Documento ────────────────────────────────────────── function apriModalDocumento() { fileSelezionatoObj = null; document.getElementById('fileInput').value = ''; const area = document.getElementById('uploadArea'); area.className = 'upload-area'; area.innerHTML = '📎Tocca per scegliere un filePDF · JPG · PNG'; document.getElementById('btnCarica').style.display = 'none'; document.getElementById('btnCarica').disabled = false; document.getElementById('btnCarica').textContent = 'CARICA'; document.getElementById('progressUpload').style.display = 'none'; document.getElementById('progressUploadFill').style.width = '0%'; document.getElementById('uploadError').style.display = 'none'; document.getElementById('modalDocumento').classList.add('open'); } function rilevaPdf(file) { return file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf'); } function fileSelezionato(input) { const f = input.files[0]; if (!f) return; fileSelezionatoObj = f; const isPdf = rilevaPdf(f); const area = document.getElementById('uploadArea'); area.className = 'upload-area has-file'; const sizeKb = (f.size / 1024).toFixed(0); const hint = isPdf ? `${sizeKb} KB` : `${sizeKb} KB → verrà compressa`; area.innerHTML = `${isPdf ? '📄' : '🖼️'}${f.name}${hint}`; document.getElementById('btnCarica').style.display = ''; } async function comprimeImmagine(file) { if (rilevaPdf(file)) return file; return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onerror = reject; reader.onload = e => { const img = new Image(); img.onerror = () => resolve(file); img.onload = () => { const MAX = 1600; const scale = Math.min(1, MAX / Math.max(img.width, img.height)); const canvas = document.createElement('canvas'); canvas.width = Math.round(img.width * scale); canvas.height = Math.round(img.height * scale); canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height); canvas.toBlob(blob => { resolve(new File([blob], file.name.replace(/\.(png|jpeg|jpg)$/i, '.jpg'), { type: 'image/jpeg' })); }, 'image/jpeg', 0.82); }; img.src = e.target.result; }; reader.readAsDataURL(file); }); } function bufToB64(buffer) { let s = ''; const b = new Uint8Array(buffer); for (let i = 0; i < b.length; i += 8192) s += String.fromCharCode.apply(null, b.subarray(i, i + 8192)); return btoa(s); } async function comprimiGzip(buffer) { try { const cs = new CompressionStream('gzip'); const w = cs.writable.getWriter(); w.write(new Uint8Array(buffer)); w.close(); const chunks = []; const rd = cs.readable.getReader(); while (true) { const { done, value } = await rd.read(); if (done) break; chunks.push(value); } const tot = chunks.reduce((a, c) => a + c.length, 0); const out = new Uint8Array(tot); let off = 0; for (const c of chunks) { out.set(c, off); off += c.length; } return out.buffer; } catch(e) { return buffer; } } async function decomprimiGzip(buffer) { const ds = new DecompressionStream('gzip'); const w = ds.writable.getWriter(); w.write(new Uint8Array(buffer)); w.close(); const chunks = []; const rd = ds.readable.getReader(); while (true) { const { done, value } = await rd.read(); if (done) break; chunks.push(value); } const tot = chunks.reduce((a, c) => a + c.length, 0); const out = new Uint8Array(tot); let off = 0; for (const c of chunks) { out.set(c, off); off += c.length; } return out.buffer; } async function uploadDocumento() { if (!fileSelezionatoObj) return; const btn = document.getElementById('btnCarica'); const prog = document.getElementById('progressUpload'); const fill = document.getElementById('progressUploadFill'); btn.disabled = true; btn.textContent = 'Comprimo…'; prog.style.display = ''; fill.style.width = '10%'; document.getElementById('uploadError').style.display = 'none'; try { let b64, tipoFinale, dimensioneFinale; if (rilevaPdf(fileSelezionatoObj)) { const buf = await fileSelezionatoObj.arrayBuffer(); fill.style.width = '30%'; const gz = await comprimiGzip(buf); tipoFinale = 'application/pdf'; if (gz.byteLength < buf.byteLength) { b64 = 'gz:' + bufToB64(gz); dimensioneFinale = gz.byteLength; } else { b64 = bufToB64(buf); dimensioneFinale = buf.byteLength; } } else { const imgCompressed = await comprimeImmagine(fileSelezionatoObj); fill.style.width = '30%'; tipoFinale = imgCompressed.type; dimensioneFinale = imgCompressed.size; b64 = await new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = e => resolve(e.target.result.split(',')[1]); reader.onerror = reject; reader.readAsDataURL(imgCompressed); }); } fill.style.width = '65%'; btn.textContent = 'Caricamento…'; const res = await fetch(`${URL}/documenti_valigia`, { method: 'POST', headers: H, body: JSON.stringify({ valigia_id: valigiaCorrenteId, email_utente: userEmail, nome: fileSelezionatoObj.name, tipo: tipoFinale, data: b64, dimensione: dimensioneFinale }) }); fill.style.width = '100%'; if (!res.ok) { let detail = `HTTP ${res.status}`; try { const j = await res.json(); detail += ': ' + (j.message || j.hint || j.code || JSON.stringify(j)); } catch(e) {} throw new Error(detail); } const rispKb = Math.round((fileSelezionatoObj.size - dimensioneFinale) / 1024); const msg = rispKb > 0 ? `✓ Caricato · ${(dimensioneFinale/1024).toFixed(0)} KB (risparmiati ${rispKb} KB)` : `✓ Documento caricato`; setTimeout(() => { document.getElementById('modalDocumento').classList.remove('open'); caricaDocumenti(); toast(msg); }, 400); } catch(e) { const errDiv = document.getElementById('uploadError'); errDiv.textContent = e.message || 'Caricamento fallito'; errDiv.style.display = ''; btn.disabled = false; btn.textContent = 'RIPROVA'; prog.style.display = 'none'; fill.style.width = '0%'; } } // ── View 5: Note ───────────────────────────────────────────── let notaEditId = null; function _noteKey() { return `notes_valigia_${valigiaCorrenteId}`; } function _getNotes() { // Migrazione dal vecchio formato stringa singola const oldKey = `note_valigia_${valigiaCorrenteId}`; const oldVal = localStorage.getItem(oldKey); if (oldVal !== null) { const notes = oldVal.trim() ? [{ id: Date.now(), testo: oldVal.trim(), creatoIl: new Date().toISOString() }] : []; localStorage.setItem(_noteKey(), JSON.stringify(notes)); localStorage.removeItem(oldKey); return notes; } try { return JSON.parse(localStorage.getItem(_noteKey()) || '[]'); } catch(e) { return []; } } function _setNotes(notes) { localStorage.setItem(_noteKey(), JSON.stringify(notes)); } function _fmtData(iso) { const d = new Date(iso); return d.toLocaleDateString('it-IT', { day: 'numeric', month: 'short', year: 'numeric' }) + ', ' + d.toLocaleTimeString('it-IT', { hour: '2-digit', minute: '2-digit' }); } function apriNote() { sessionStorage.setItem('va_view', 'viewNote'); showView('viewNote'); renderNote(); } function renderNote() { const notes = _getNotes(); const el = document.getElementById('listaNote'); if (!notes.length) { el.innerHTML = `
📝 Nessuna nota ancora
Tocca + per aggiungerne una
`; return; } el.innerHTML = notes.map(n => { const preview = n.testo.trim(); return `
${preview}
${_fmtData(n.creatoIl)}
`; }).join(''); } function apriNuovaNota() { notaEditId = null; document.getElementById('notaTextarea').value = ''; document.getElementById('btnEliminaNota').style.display = 'none'; document.getElementById('modalNota').classList.add('open'); setTimeout(() => document.getElementById('notaTextarea').focus(), 300); } function apriEditNota(id) { notaEditId = id; const notes = _getNotes(); const nota = notes.find(n => n.id === id); if (!nota) return; document.getElementById('notaTextarea').value = nota.testo; document.getElementById('btnEliminaNota').style.display = ''; document.getElementById('modalNota').classList.add('open'); setTimeout(() => document.getElementById('notaTextarea').focus(), 300); } function salvaNotaModal() { const testo = document.getElementById('notaTextarea').value.trim(); if (!testo) { toast('Scrivi qualcosa prima di salvare'); return; } const notes = _getNotes(); if (notaEditId !== null) { const idx = notes.findIndex(n => n.id === notaEditId); if (idx !== -1) notes[idx].testo = testo; } else { notes.unshift({ id: Date.now(), testo, creatoIl: new Date().toISOString() }); } _setNotes(notes); document.getElementById('modalNota').classList.remove('open'); renderNote(); toast('✓ Nota salvata'); } function eliminaNotaCorrente() { if (notaEditId === null) return; eliminaNota(notaEditId); document.getElementById('modalNota').classList.remove('open'); } function eliminaNota(id) { if (!confirm('Eliminare questa nota?')) return; const notes = _getNotes().filter(n => n.id !== id); _setNotes(notes); renderNote(); toast('Nota eliminata'); } // ── Close modals on backdrop ────────────────────────────────── document.querySelectorAll('.modal-overlay').forEach(m => { m.addEventListener('click', e => { if (e.target === m) m.classList.remove('open'); }); }); let toastTimer; function toast(msg) { const t = document.getElementById('toast'); t.textContent = msg; t.classList.add('show'); clearTimeout(toastTimer); toastTimer = setTimeout(() => t.classList.remove('show'), 2500); } caricaValigie(); function _fbApri() { document.getElementById('fbOverlay').classList.add('open'); document.getElementById('fbTesto').focus(); } function _fbChiudi() { document.getElementById('fbOverlay').classList.remove('open'); document.getElementById('fbTesto').value = ''; } async function _fbInvia() { const testo = document.getElementById('fbTesto').value.trim(); if (!testo) return; const tipo = document.querySelector('input[name="fbTipo"]:checked').value; const email = (typeof userEmail !== 'undefined' && userEmail) ? userEmail : (localStorage.getItem('userEmail') || 'anonimo'); const btn = document.getElementById('fbSendBtn'); btn.disabled = true; btn.textContent = 'Invio…'; try { const r = await fetch('/api/feedback', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ email, tipo, testo }) }); if (r.ok) { _fbChiudi(); if (typeof toast === 'function') toast('Feedback inviato, grazie! 🙏'); } } catch(e) {} finally { btn.disabled = false; btn.textContent = 'Invia'; } }